Я просто назначил переменную, но переменная echo $показывает что-то еще
Вот серия случаев, когда echo $var
может показывать другое значение, чем только что назначенное. Это происходит независимо от того, было ли присвоенное значение "двойным кавычками", "одиночным кавычками" или без кавычек.
Как заставить оболочку правильно установить мою переменную?
Звездочка
Ожидаемый результат: /* Foobar is free software */
, но вместо этого я получаю список имен файлов:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Квадратные скобки
Ожидаемое значение [a-z]
, но иногда я получаю только одну букву!
$ var=[a-z]
$ echo $var
c
Линейные каналы (новые строки)
Ожидаемое значение представляет собой список отдельных строк, но вместо этого все значения находятся в одной строке!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Несколько пробелов
Я ожидал тщательно выровненного заголовка таблицы, но вместо этого несколько пробелов либо исчезли, либо рухнули в один!
$ var=" title | count"
$ echo $var
title | count
Вкладка
Я ожидал два значения, разделенные вкладкой, но вместо этого получаю два значения, разделенные пробелами!
$ var=$'key\tvalue'
$ echo $var
key value
Ответы
Ответ 1
Во всех вышеперечисленных случаях переменная правильно установлена, но неправильно прочитана! Правильный способ: использовать двойные кавычки при ссылке:
echo "$var"
Это дает ожидаемое значение во всех приведенных примерах. Всегда указывайте ссылки на переменные!
Почему?
Если переменная без кавычек, она будет:
-
разбиение полей, где значение разбивается на несколько слов по пробелам (по умолчанию):
До: /* Foobar is free software */
После: /*
, Foobar
, is
, free
, software
, */
-
Каждое из этих слов будет подвергаться расширению имени пути, где шаблоны расширяются в соответствующие файлы:
До: /*
После: /bin
, /boot
, /dev
, /etc
, /home
,...
-
Наконец, все аргументы передаются в echo, который записывает их разделенные одиночными пробелами, предоставляя
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
вместо значения переменной.
Когда переменная цитируется, она будет:
- Подставлять его значение.
- Нет шага 2.
Вот почему вы должны всегда ссылаться на все ссылки на переменные, если вы специально не требуете разделения слов и расширения имени пути. Такие инструменты, как shellcheck, помогут вам и будут предупреждать о недостающих котировках во всех случаях выше.
Ответ 2
Возможно, вам захочется узнать, почему это происходит. Вместе с отличным объяснением этого другого парня найдите ссылку Почему моя оболочка script задыхается в пропуске или другом специальные символы?, написанные Gilles в Unix и Linux:
Зачем мне писать "$foo"
? Что происходит без кавычек?
$foo
не означает "принять значение переменной foo
". Это значит что-то гораздо более сложное:
- Сначала возьмите значение переменной.
- Разделение полей: рассматривайте это значение как список полей, разделенных пробелами, и создайте результирующий список. Например, если переменная содержит
foo * bar
, то результатом этого шага является 3-элемент список foo
, *
, bar
. - Генерация имени файла: обрабатывайте каждое поле как glob, т.е. как шаблон подстановочного знака, и заменяйте его на список имен файлов, соответствующих этому шаблон. Если шаблон не соответствует никаким файлам, он остается неизмененной. В нашем примере это приводит к списку, содержащему
foo
, следуя списку файлов в текущем каталоге и, наконец, bar
. Если текущий каталог пуст, результатом будет foo
, *
, bar
.
Обратите внимание, что результатом является список строк. В Синтаксис оболочки: контекст списка и контекст строки. Разделение поля и генерация имени файла происходит только в контексте списка, но большая часть время. Двойные кавычки ограничивают контекст строки: целое строка с двумя кавычками - это единственная строка, которую нельзя разделить. (Исключение: "[email protected]"
, чтобы перейти к списку позиционных параметров, например. "[email protected]"
является эквивалентно "$1" "$2" "$3"
, если есть три позиционных параметры. См. В чем разница между $* и [email protected]?)
То же самое происходит с заменой команды на $(foo)
или с помощью
`foo`
. На стороне примечания не используйте `foo`
: его правила цитирования странные и не переносные, а все современные оболочки поддерживают $(foo)
, которые абсолютно эквивалентен, за исключением наличия интуитивных правил цитирования.
Результат арифметической подстановки также претерпевает одинаковые расширения, но это обычно не вызывает беспокойства, поскольку оно содержит только нерасширяемые символы (при условии, что IFS
не содержит цифр или -
).
См. Когда требуется двойное цитирование? для получения более подробной информации о случаи, когда вы можете оставить цитаты.
Если вы не хотите, чтобы все это происходило, просто запомните всегда используйте двойные кавычки вокруг переменных и подстановок команд. Делать позаботьтесь: отказ от цитат может привести не только к ошибкам, но и к безопасность отверстия.
Ответ 3
Вывод echo $var
сильно зависит от значения переменной IFS
. По умолчанию он содержит символы пробела, табуляции и новой строки:
[[email protected] ~]$ echo -n "$IFS" | cat -vte
^I$
Это означает, что когда оболочка выполняет разбиение поля (или разбиение слов), он использует все эти символы в качестве разделителей слов. Это то, что происходит при ссылке на переменную без двойных кавычек, чтобы ее эхо ($var
), и поэтому ожидаемый результат изменяется.
Один из способов предотвратить разделение слов (помимо использования двойных кавычек) - установить IFS
в null. См. http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05:
Если значение IFS равно нулю, расщепление поля не должно выполняться.
Установка в значение null означает, что установка пуста
Значение:
IFS=
Тест:
[[email protected] ~]$ echo -n "$IFS" | cat -vte
^I$
[[email protected] ~]$ var=$'key\nvalue'
[[email protected] ~]$ echo $var
key value
[[email protected] ~]$ IFS=
[[email protected] ~]$ echo $var
key
value
[[email protected] ~]$
Ответ 4
двойную цитату пользователя, чтобы получить точное значение. как это:
echo "${var}"
и он правильно прочитает ваше значение.
Ответ 5
В дополнение к другим проблемам, вызванным -n
процитировать, -n
и -e
могут использоваться echo
качестве аргументов. (Только первое допустимо в соответствии со спецификацией POSIX для echo
, но несколько распространенных реализаций также нарушают спецификацию и используют -e
).
Чтобы избежать этого, используйте printf
вместо echo
когда детали имеют значение.
Таким образом:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
Однако правильное цитирование не всегда спасет вас при использовании echo
:
$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed
... в то время как это сэкономит вам printf
:
$ vars="-n"
$ printf '%s\n' "$vars"
-n
Ответ 6
Дополнительно для размещения переменной в кавычках можно также преобразовать вывод переменной с помощью tr
и преобразовать пробелы в строки новой строки.
$ echo $var | tr " " "\n"
foo
bar
baz
Хотя это немного запутаннее, он добавляет больше разнообразия с выходом, поскольку вы можете заменить любой символ как разделитель между переменными массива.