Вот серия случаев, когда 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
var=$(cat file)
что нормально, ноecho "$var"
нужно.Ответы:
Во всех вышеперечисленных случаях переменная установлена правильно, но неправильно прочитана! Правильный способ - использовать двойные кавычки при ссылке :
Это дает ожидаемое значение во всех приведенных примерах. Всегда указывайте ссылки на переменные!
Зачем?
Когда переменная не заключена в кавычки , она:
Пройдите разделение поля, где значение разбивается на несколько слов по пробелу (по умолчанию):
Перед:
/* Foobar is free software */
После того, как :
/*
,Foobar
,is
,free
,software
,*/
Каждое из этих слов подвергнется расширению имени пути , где шаблоны будут преобразованы в соответствующие файлы:
Перед:
/*
После того, как :
/bin
,/boot
,/dev
,/etc
,/home
, ...Наконец, все аргументы передаются в echo, которое записывает их через одиночные пробелы , давая
вместо значения переменной.
Когда переменная цитируется, она:
Вот почему вы всегда должны заключать в кавычки все ссылки на переменные , если только вам не требуется специально разбиение слов и расширение имени пути. Такие инструменты, как shellcheck, могут помочь и предупредить об отсутствующих кавычках во всех вышеупомянутых случаях.
источник
$(..)
обрезает завершающие переводы строки. Вы можете использовать,var=$(cat file; printf x); var="${var%x}"
чтобы обойти это.Возможно, вы захотите узнать, почему это происходит. Вместе с прекрасным объяснением того другого парня найдите ссылку на то, почему мой сценарий оболочки задыхается от пробелов или других специальных символов? написано Жилем в Unix и Linux :
источник
В дополнение к другим вопросам , из- за невозможности процитировать,
-n
и-e
может потребляться вecho
качестве аргументов. (Только первое является законным в соответствии со спецификацией POSIXecho
, но несколько распространенных реализаций нарушают спецификацию и также потребляют-e
).Чтобы этого избежать, используйте
printf
вместо,echo
когда важны детали.Таким образом:
Однако правильное цитирование не всегда спасает вас при использовании
echo
:... в то время как он будет спасать вас
printf
:источник
-e
/-n
/ не отображается?" Мы можем добавлять ссылки отсюда по мере необходимости.-n
в виду потреблять ?-e
. Стандарт дляecho
не определяет вывод, когда его первый аргумент имеет значение-n
, что делает любой / все возможные выходные данные допустимыми в этом случае; такого положения нет-e
.двойные кавычки пользователя, чтобы получить точное значение. как это:
и он правильно прочитает ваше значение.
источник
echo $var
вывод сильно зависит от значенияIFS
переменной. По умолчанию он содержит символы пробела, табуляции и новой строки:Это означает, что когда оболочка выполняет разделение полей (или разделение слов), она использует все эти символы в качестве разделителей слов. Вот что происходит при ссылке на переменную без двойных кавычек, чтобы повторить ее (
$var
), и, таким образом, ожидаемый результат изменяется.Один из способов предотвратить разделение слов (помимо использования двойных кавычек) - установить
IFS
значение null. См. Http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :Установка на null означает установку на пустое значение:
Тест:
источник
set -f
предотвратить появление глобусовIFS
установлено значение null,echo $var
будет расширяться до,echo '/* Foobar is free software */'
а расширение пути не выполняется внутри строк, заключенных в одинарные кавычки.mkdir "/this thing called Foobar is free software etc/"
увидите, что он все еще расширяется. Для[a-z]
примера это очевидно более практично .[a-z]
например , это имеет смысл .Ответ от ks1322 помог мне определить проблему при использовании
docker-compose exec
:Если опустить
-T
флаг,docker-compose exec
добавить специальный символ, который прерывает вывод, мы видимb
вместо1b
:С
-T
флагомdocker-compose exec
работает как положено:источник
Помимо помещения переменной в кавычки, можно также перевести вывод переменной, используя
tr
и преобразовывая пробелы в символы новой строки.Хотя это немного более запутанно, это добавляет больше разнообразия в вывод, поскольку вы можете заменить любой символ в качестве разделителя между переменными массива.
источник
tr
другой способ создания массивов из текстовых файлов.tr
необходимости правильно / правильно создавать массив из текстового файла - вы можете указать любой разделитель, который хотите, установив IFS. Например:IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')
работает вплоть до bash 3.2 (самая старая широко распространенная версия) и правильно устанавливает статус выхода в false, если выcat
потерпели неудачу. И если вы хотите, скажем, вкладки вместо перевода строки, вы бы просто заменить$'\n'
с$'\t'
.arrayname=( $( cat file | tr '\n' ' ' ) )
, то это разбито на нескольких уровнях: оно объединяет ваши результаты (так что a*
превращается в список файлов в текущем каталоге), и он будет работать так же хорошо безtr
( илиcat
, если на то пошло; можно было просто использовать,arrayname=$( $(<file) )
и он был бы сломан таким же образом, но менее неэффективно).