Как назначить строковое значение переменной в несколько строк при отступе?

54

Проблема:

  1. Мне нужно присвоить переменной значение, которое прилично долго.
  2. Все строки моего скрипта должны быть под определенным количеством столбцов.

Итак, я пытаюсь назначить его, используя более одной строки.

Обойтись без отступов просто:

VAR="This displays without \
any issues."
echo "${VAR}"

Результат:

This displays without any issues.

Однако с отступами:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

Результат:

This displays with      extra spaces.

Как я могу элегантно назначить его без этих пробелов?

Sman865
источник

Ответы:

31

Здесь проблема в том, что вы окружаете переменную двойными кавычками (""). Удалите его, и все будет работать нормально.

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

Выход

 This displays with extra spaces.

Здесь проблема в том, что двойная кавычка переменной сохраняет все символы пробела. Это может быть использовано в случае, если вам это явно необходимо.

Например,

$ echo "Hello     World    ........ ...            ...."

распечатает

Hello     World    ........ ...            ....

И на удаление цитаты, его разные

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

Здесь Bash удаляет лишние пробелы в тексте, потому что в первом случае весь текст воспринимается как «одиночный» аргумент и, таким образом, сохраняется лишние пробелы. Но во втором случае echoкоманда получает текст в виде 5 аргументов.

Заключение в кавычки переменной также будет полезно при передаче аргументов командам.

В приведенной ниже команде echoтолько получает один аргумент как"Hello World"

$ variable="Hello World"
$ echo "$variable"

Но в случае нижеприведенного сценария echoполучает два аргумента как HelloиWorld

$ variable="Hello World"
$ echo $variable
Каннан Мохан
источник
Большинство ответов работали хорошо, но это было самым простым. Спасибо!
Sman865
12
@ Sman865 - поверьте мне, когда я скажу вам, что на самом деле это почти наверняка самый сложный ответ, предложенный здесь, и он также неверен в большинстве случаев - особенно в его вступительном заявлении. Любая проблема, связанная с присваиванием значения, никоим образом не может быть связана с его последующим расширением - это просто назад. Извините, Каннан, но этот ответ и неправильный, и неправильный. Раскрытие поля для расширения $IFSявляется мощным и универсальным инструментом, но код, написанный таким образом, никогда не сможет дать надежных результатов любого рода.
mikeserv
2
Если не использовать кавычки при раскрытии переменной, рано или поздно возникают проблемы, начиная с раскрытия имени файла (любого из * ? []).
mr.spuratic
Я согласен, что переменные должны быть заключены в двойные кавычки, чтобы предотвратить расширение bash. Но для особых случаев мы можем избежать этого, чтобы предотвратить усложнение кода.
Каннан Мохан,
1
Я согласен с @mikeserv, это очень плохой совет, если принимать его за чистую монету. Это сломается, если используется без последствий. Например, если переменная передается в качестве аргумента команде, вы не хотите, чтобы каждое слово разбивалось как отдельный аргумент.
haridsv
28

Решения, предложенные esuoxu и Mickaël Bucas, являются распространенным и более портативным способом сделать это.

Вот несколько bashрешений (некоторые из которых также должны работать в других оболочках, например zsh). Во-первых, с помощью +=оператора добавления (который работает немного по-разному для каждой из целочисленной переменной, регулярной переменной и массива).

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

Если вам нужны новые строки (или другие пробелы / экранированные символы) в тексте, используйте $''вместо этого кавычки:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

Затем, используя, printf -vчтобы назначить форматированное значение переменной

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

Хитрость заключается в том, что аргументов больше, чем спецификаторов формата, поэтому в отличие от большинства printfфункций, bash повторно использует строку формата, пока не закончится. Вы можете поместить \nвнутри строки формата, или использовать $ '', (или оба) для работы с пробелами.

Далее с помощью массива:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

Вы также можете использовать +=для построения текста построчно (обратите внимание на ()):

text+=("post script")

Здесь, однако, вы должны помнить, чтобы "сгладить" массив, если вы хотите, чтобы весь текстовый контент за один раз

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

(целочисленные индексированные массивы неявно сортируются, в отличие от ассоциативных массивов). Это дает вам немного больше гибкости, так как вы можете манипулировать линиями и даже вырезать и нарезать кубики, если это необходимо.

Наконец, используя readили readarrayи «здесь-документ»:

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

Форма здесь-документа <<-означает, что все ведущие жесткие вкладки удаляются из ввода, поэтому вы должны использовать вкладки для отступа вашего текста. Кавычки "EOT"не позволяют расширить возможности оболочки, поэтому ввод используется дословно. При readэтом используется ввод с разделением байтов NUL, так что он будет читать текст с разделителями новой строки за один раз. С помощью readarray(он же mapfileдоступен с bash-4.0) он считывает в массив и -tразбивает символы новой строки в каждой строке.

mr.spuratic
источник
1
readВариант с here documentочень приятно! Полезно для встраивания скриптов Python и выполнения с python -c. Я раньше делал script=$(cat <here document>), но read -r -d '' script <here document>гораздо лучше.
haridsv
9

Существует специальный синтаксис heredoc, который удаляет вкладки в начале всех строк: «<< -» (обратите внимание на добавленную черту)

http://tldp.org/LDP/abs/html/here-docs.html

Пример 19-4. Многострочное сообщение с вкладками подавлено

Вы можете использовать это так:

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

Результат:

A
B
C

Работает только с вкладками, а не пробелами.

Микаэль Букас
источник
Здесь, в Ubuntu, верно обратное - пробелы работают, а не вкладки. \tотлично работает, хотя, если вам нужны вкладки. Просто используйте, echo -e "$v"а не echo "$v"включайте символы обратной косой черты
will-ob
5

Пусть оболочка съест нежелательные переводы строки и следующие пробелы:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

Так что это возможно ... но, конечно, дело вкуса, нравится или не нравится это решение ...

йети
источник
3
каждый из них - вилка.
mikeserv
5

Может быть, вы можете попробовать это.

          echo "Test" \
               "Test2" \
               "Test3"
esuoxu
источник
5

Вот как я предлагаю вам сделать это, и я объясню почему, но сначала я хочу поговорить о чем-то еще ...

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

Многие другие предлагаемые здесь решения, похоже, предполагают, что вы можете каким-то образом повлиять на содержимое переменной оболочки, изменив методы ее расширения. Уверяю вас, это не тот случай.

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

ВЫХОД

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

То, что вы видите выше, это сначала расширение с разделением полей, затем отчет о количестве байтов для исходной переменной расширения, затем расширение с разделителями кавычками и тот же счетчик байтов. Хотя выходные данные могут отличаться, содержимое переменной оболочки $stringникогда не изменяется вообще, кроме как при присваивании.

Более того, если вы не понимаете, почему это так, вы непременно столкнетесь с некоторыми очень неприятными сюрпризами раньше, чем позже. Давайте попробуем это снова, но в немного других условиях.

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

То же самое $string- другая среда.

ВЫХОД

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

Разделение полей происходит на основе разделителей полей, определенных в $IFS. Есть два вида разделителей - $IFSпробелы и $IFSвсе остальное. По умолчанию $IFSназначается новая строка на вкладке пробела - это три возможных $IFSзначения пробела. Однако, как вы можете видеть выше, его легко изменить, и он может оказать радикальное влияние на расщепление полей.

$IFSпробельные символы будут последовательно перемещаться в одно поле - и именно поэтому echoрасширение, содержащее любую последовательность пробелов, если оно $IFSсодержит пробел, будет вычисляться только в один пробел - потому что echoобъединяет свои аргументы в пробелах. Но любые непробельные значения не будут вытесняться одинаково, и каждый встречающийся разделитель всегда получает поле для себя - как видно из раскрытия материала выше.

Это не самое худшее. Считай это другим $string.

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

ВЫХОД

* * * * * * 30
 * * *                  * * *  30

Выглядит хорошо, верно? Что ж, давайте снова изменим окружающую среду.

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

ВЫХОД

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

Ого.

По умолчанию оболочка будет расширять глобусы имен файлов, если она может соответствовать им. Это происходит после раскрытия параметра и разделения поля в его порядке разбора, поэтому любая строка без кавычек уязвима в этом смысле. Вы можете отключить это поведение, set -fесли хотите, но любая POSIX-совместимая оболочка всегда будет по умолчанию.

Это тот тип вещей, с которым вы сталкиваетесь, когда выбрасываете кавычки в расширениях в соответствии со своими предпочтениями отступов. И даже в этом случае, в любом случае, независимо от его поведения расширения, фактическое значение для $stringвсегда остается таким, каким оно было при последнем назначении. Итак, вернемся к первому.

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

ВЫХОД

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

Я считаю, что это гораздо более разумный способ адаптации синтаксиса оболочки к вашим предпочтениям отступов. То, что я делаю выше, - это назначение каждой отдельной строки позиционному параметру - на который можно ссылаться по номеру, например, $1или ${33}-, а затем присвоение их сцепленных значений с $varпомощью специального параметра оболочки $*.

Этот подход не застрахован от $IFSэтого. Тем не менее, я считаю, что это связано с $IFSдополнительным преимуществом в этом отношении. Рассмотреть возможность:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

ВЫХОД

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

Как видите, $*объединяет каждый аргумент в "$@"первом байте $IFS. Таким образом, при сохранении значения $IFSпо-разному назначаются разные разделители полей для каждого сохраненного значения. Между прочим, вы видите буквальное значение для каждой переменной. Если вы вообще не хотите использовать разделитель, вы должны сделать:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

ВЫХОД

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
mikeserv
источник
2

Вы можете попробовать:

echo $VAR | tr -s " "

или же

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

а также вы можете проверить это .

Вивиан Майя
источник
2

Используйте расширение замещения Bash

Если вы используете Bash, вы можете использовать расширение замещения . Например:

$ echo "${VAR//  /}"
This displays with extra spaces.
CodeGnome
источник
1

Это вариант для установки переменных типа пути:

set -- "${MYDIR}/usr/local/lib" \
      :"${MYDIR}/usr/lib" \
      :"${MYDIR}/lib" \
       "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export LD_LIBRARY_PATH="$*"
    LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)

Использование setперезаписей $@, которые можно сохранить и использовать позже следующим образом:

ARGV=("$@")
exec foo "${ARGV[@]}"

В LD_LIBRARY_PATH=$(sed 's/ :/:/g' <<< $LD_LIBRARY_PATH)линии Устраняет пространства перед двоеточием, а также возможно замыкающие пробелы. Если только устранение завершающих пробелов, используйте LD_LIBRARY_PATH=${LD_LIBRARY_PATH%% }вместо этого.

Весь этот подход является вариантом превосходного ответа Mikeserv.

rsanden
источник
0

Не бойтесь пробелов. Просто удалите их, прежде чем печатать многострочный текст.

$ cat ./t.sh
#!/bin/bash

NEED_HELP=1
if [[ $NEED_HELP -eq 1 ]]; then

    lucky_number=$((1 + RANDOM % 10 + 10))

    read -r -d '' text <<'    EOF'
    NAME says hello

    Look at this helpful text:

                                                 * -**
                                 Eyjafjallajokull        Eyjafjallajokull
                            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

    Don't go away, please rate your experience... It'll just take two minutes.

    EOF
    text=$(echo "$text" | sed -r 's!^\s{4}!!')
    text=$(echo "$text" | sed -r "s!\bNAME\b!$0!") # Bash: text=${text//NAME/$0}
    text=$(echo "$text" | sed -r "s!\btwo\b!$lucky_number!")

    echo "$text"

fi

Выход:

$ ./t.sh
./t.sh says hello

Look at this helpful text:

                                             * -**
                             Eyjafjallajokull        Eyjafjallajokull
                        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
                Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
            Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
        Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
    Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull
Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull Eyjafjallajokull

Don't go away, please rate your experience... It'll just take 16 minutes.

Нет необходимости использовать <<-heredoc и разбить отступ в 4 пробела символами табуляции.

c0xc
источник