Я только что назначил переменную, но echo $variable показывает что-то еще

Вот серия случаев, когда 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

6 ответов

Решение

Во всех вышеперечисленных случаях переменная установлена ​​правильно, но не правильно читается! Правильный способ заключается в использовании двойных кавычек при ссылке:

echo "$var"

Это дает ожидаемое значение во всех приведенных примерах. Всегда цитируйте переменные ссылки!


Зачем?

Когда переменная не заключена в кавычки, она будет:

  1. Выполните разбиение поля, где значение разбивается на несколько слов в пробеле (по умолчанию):

    До: /* Foobar is free software */

    После: /*, Foobar, is, free, software, */

  2. Каждое из этих слов будет подвергаться расширению пути, где шаблоны будут расширены в соответствующие файлы:

    До: /*

    После: /bin, /boot, /dev, /etc, /home...

  3. Наконец, все аргументы передаются в echo, который записывает их через пробел, давая

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
    

    вместо значения переменной.

Когда переменная заключена в кавычки, она будет:

  1. Подменяйте его значение.
  2. Нет шага 2.

Вот почему вы всегда должны заключать в кавычки все ссылки на переменные, если только вам не требуется разделение слов и расширение пути. Такие инструменты, как shellcheck, помогут вам и предупредят о пропущенных кавычках во всех случаях выше.

Вы можете узнать, почему это происходит. Вместе с отличным объяснением этого другого парня найдите ссылку на вопрос: почему мой сценарий оболочки задыхается от пробелов или других специальных символов? написанный Valters Vingolds в Unix и Linux:

Зачем мне писать "$foo"? Что происходит без кавычек?

$foo не означает "принять значение переменной foo ". Это означает что-то гораздо более сложное:

  • Сначала возьмите значение переменной.
  • Разделение полей: обработайте это значение как список полей, разделенных пробелами, и создайте результирующий список. Например, если переменная содержит foo * bar ​ тогда результатом этого шага будет 3-х элементный список foo, *, bar,
  • Генерация имени файла: обрабатывать каждое поле как глобус, т. Е. Как шаблон с подстановочными знаками, и заменять его списком имен файлов, соответствующих этому шаблону. Если шаблон не соответствует ни одному файлу, он остается неизменным. В нашем примере это приводит к списку, содержащему foo после списка файлов в текущем каталоге и, наконец, bar, Если текущий каталог пуст, результат foo, *, bar,

Обратите внимание, что результатом является список строк. В синтаксисе оболочки есть два контекста: контекст списка и строковый контекст. Разделение полей и генерация файлов происходят только в контексте списка, но это происходит в большинстве случаев. Двойные кавычки разделяют строковый контекст: вся строка в двойных кавычках представляет собой одну строку, которую нельзя разделять. (Исключение: "$@" расширить до списка позиционных параметров, например, "$@" эквивалентно "$1" "$2" "$3" если есть три позиционных параметра. См. В чем разница между $* и $ @?)

То же самое происходит с подстановкой команд $(foo) или с `foo`, На заметку, не используйте `foo`: его правила цитирования странные и непереносимые, и все современные оболочки поддерживают $(foo) что абсолютно эквивалентно, за исключением наличия интуитивно понятных правил цитирования.

Вывод арифметической подстановки также подвергается тем же расширениям, но обычно это не представляет проблемы, так как содержит только нерасширяемые символы (при условии, что IFS не содержит цифр или -).

См. Когда необходимо двойное цитирование? для получения более подробной информации о случаях, когда вы можете опустить цитаты.

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

В дополнение к другим проблемам, вызванным невозможностью процитировать, -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

Пользователь двойная кавычка, чтобы получить точное значение. как это:

echo "${var}"

и он будет правильно читать вашу ценность.

echo $var выход сильно зависит от стоимости IFS переменная. По умолчанию он содержит символы пробела, табуляции и новой строки:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$

Это означает, что когда оболочка выполняет разделение полей (или разделение слов), она использует все эти символы в качестве разделителей слов. Вот что происходит при обращении к переменной без двойных кавычек для ее отображения ($var) и, следовательно, ожидаемый результат изменяется.

Одним из способов предотвращения разделения слов (помимо использования двойных кавычек) является установка IFS к нулю. См. http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html:

Если значение IFS равно нулю, разделение полей не производится.

Установка на ноль означает установку на пустое значение:

IFS=

Тестовое задание:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$ 

Ответ от ks1322 помог мне определить проблему при использованииdocker-compose exec:

Если вы опустите -T флаг, docker-compose exec добавляем специальный символ, который прерывает вывод, мы видим b вместо того 1b:

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

С -T флаг, docker-compose exec работает как положено:

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b

В дополнение к помещению переменной в кавычку, можно также перевести вывод переменной, используя tr и преобразование пробелов в переводы строк.

$ echo $var | tr " " "\n"
foo
bar
baz

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

Другие вопросы по тегам