Я всегда очень не решаюсь возиться с этим, $IFS
потому что это заглушает глобальное.
Но часто это делает загрузку строк в массив bash приятной и лаконичной, а для сценариев bash трудно получить краткость.
Поэтому я полагаю, что может быть лучше, чем ничего, если я попытаюсь «сохранить» начальное содержимое $IFS
в другой переменной, а затем восстановить его сразу после того, как я что-то использую $IFS
.
Это практично? Или это, по сути, бессмысленно, и я должен просто IFS
вернуться к тому, чем он должен быть для его последующего использования?
bash
shell-script
Стивен Лу
источник
источник
$' \t\n'
если вы используете bash.unset $IFS
просто не всегда восстанавливать его до того, что вы ожидаете по умолчанию.Ответы:
Вы можете сохранить и присвоить IFS по мере необходимости. В этом нет ничего плохого. Нередко сохраняют его значение для восстановления после временной, оперативной модификации, как в примере с присваиванием массива.
Как упоминает @llua в своем комментарии к вашему вопросу, простое отключение IFS восстановит поведение по умолчанию, эквивалентное назначению пробела-табуляции-новой строки.
Стоит подумать о том, как может быть более проблематично не устанавливать / не устанавливать IFS явно, чем делать это.
Из версии POSIX 2013, 2.5.3 Переменные оболочки :
Вызванная POSIX-совместимая оболочка может наследовать или не наследовать IFS из своего окружения. Из этого следует:
"$*"
), но который может выполняться под оболочкой, которая инициализирует IFS из среды, должен явно установить / сбросить IFS, чтобы защитить себя от вторжения в окружающую среду.NB. Важно понимать, что для этого обсуждения слово «вызванный» имеет особое значение. Оболочка вызывается только тогда, когда она вызывается явно, используя свое имя (включая
#!/path/to/shell
шебанг). Подоболочка - например, которая может быть создана$(...)
илиcmd1 || cmd2 &
- не является вызванной оболочкой, и ее IFS (вместе с большей частью среды выполнения) идентичен родительскому. Вызванная оболочка устанавливает значение$
pid, в то время как подоболочки наследуют его.Это не просто педантичное разоблачение; в этой области существует фактическое расхождение. Вот краткий скрипт, который тестирует сценарий с использованием нескольких различных оболочек. Он экспортирует измененный IFS (установленный в
:
) в вызванную оболочку, которая затем печатает свой IFS по умолчанию.IFS обычно не помечен для экспорта, но, если это так, обратите внимание, как bash, ksh93 и mksh игнорируют окружение
IFS=:
, в то время как dash и busybox это соблюдают.Некоторая информация о версии:
Хотя bash, ksh93 и mksh не инициализируют IFS из среды, они реэкспортируют свои модифицированные IFS.
Если по какой-либо причине вам нужно переносить IFS через среду, вы не можете сделать это, используя сам IFS; вам нужно будет присвоить значение другой переменной и пометить эту переменную для экспорта. Затем дети должны будут явно присвоить это значение своим IFS.
источник
IFS
значение в большинстве ситуаций, где оно должно использоваться, и поэтому зачастую не очень продуктивно даже пытаться «сохранить» его первоначальное значение.read
s или ссылки в двойных кавычках$*
. Этот список просто не в моей голове, поэтому он может быть не исчерпывающим (особенно при рассмотрении POSIX-расширений современных оболочек).Как правило, рекомендуется возвращать условия по умолчанию.
Однако в этом случае не так уж и много.
Почему?:
$' \t\n'
.unset IFS
заставляет его действовать так, как если бы он был установлен по умолчанию .Кроме того, хранение значения IFS имеет проблему.
Если исходный IFS был не установлен, код
IFS="$OldIFS"
установит IFS на""
, а не на его сброс.Чтобы фактически сохранить значение IFS (даже если оно не установлено), используйте это:
источник
bash
,unset IFS
не в неустановленный МФС , если она была объявлена локальной в родительском контексте (контексте функции) , а не в текущем контексте.Вы правы быть нерешительным по поводу того, чтобы нанести удар всему миру. Не бойтесь, можно написать чистый рабочий код, никогда не изменяя фактическую глобальность
IFS
или не делая громоздкий и подверженный ошибкам танец сохранения / восстановления.Вы можете:
установить IFS для одного вызова:
или
установить IFS внутри подоболочки:
Примеры
Чтобы получить разделенную запятыми строку из массива:
Примечание. Предназначено
-
только для защиты пустого массиваset -u
путем предоставления значения по умолчанию, если оно не установлено (в данном случае это значение является пустой строкой) .IFS
Модификация применима только внутри подоболочки порождены$()
подстановки команд . Это связано с тем, что у подоболочек есть копии переменных вызывающей оболочки, и поэтому они могут читать их значения, но любые изменения, выполняемые подоболочкой, влияют только на копию подоболочки, а не на родительскую переменную.Вы также можете подумать: почему бы не пропустить subshell и просто сделать это:
Здесь нет вызова команды, и эта строка вместо этого интерпретируется как два независимых последовательных назначения переменных, как если бы это было:
Наконец, давайте объясним, почему этот вариант не будет работать:
Команда
echo
действительно будет вызываться сIFS
переменной, установленной в,
, ноecho
это не заботится и не используетсяIFS
. Волшебство расширения"${array[*]}"
до строки выполняется самой (суб) оболочкой до того, какecho
она даже была вызвана.Чтобы прочитать весь файл (который не содержит
NULL
байтов) в одну переменную с именемVAR
:Примечание:
IFS=
это то же самое, чтоIFS=""
иIFS=''
, все из которых устанавливают IFS в пустую строку, что очень отличается отunset IFS
: еслиIFS
не установлено, поведение всех функциональных возможностей bash, которые используются внутри,IFS
точно такое же, как если быIFS
имело значение по умолчанию$' \t\n'
.Установка
IFS
пустой строки обеспечивает сохранение начальных и конечных пробелов.Оператор
-d ''
or-d ""
сообщает read только для того, чтобы остановить текущий вызов наNULL
байте вместо обычного перевода строки.Чтобы разделить
$PATH
его по:
разделителям:Этот пример чисто иллюстративный. В общем случае, когда вы разделяете разделитель, отдельные поля могут содержать (экранированную версию) этот разделитель. Подумайте о попытке прочитать строку
.csv
файла, столбцы которого могут содержать запятые (экранированные или заключенные в кавычки каким-либо образом). Приведенный выше фрагмент не будет работать, как предназначено для таких случаев.Тем не менее, вы вряд ли встретите такие-
:
содержащие пути внутри$PATH
. Хотя в именах путей UNIX / Linux разрешено содержать a:
, кажется, что bash не сможет обработать такие пути в любом случае, если вы попытаетесь добавить их в свой$PATH
и сохранить в них исполняемые файлы, так как нет кода для анализа двоеточий / кавычек : исходный код bash 4.4 .Наконец, обратите внимание, что фрагмент добавляет завершающий символ новой строки к последнему элементу результирующего массива (как вызвано @ StéphaneChazelas в теперь удаленных комментариях), и что если входные данные являются пустой строкой, выходные данные будут одноэлементными. массив, где элемент будет состоять из символа новой строки (
$'\n'
).мотивация
Базовый
old_IFS="${IFS}"; command; IFS="${old_IFS}"
подход, который касается глобального,IFS
будет работать, как ожидается, для самых простых сценариев. Однако, как только вы добавите любую сложность, она может легко развалиться и вызвать тонкие проблемы:command
это bash-функция, которая также изменяет глобальнуюIFS
(либо непосредственно, либо скрытую от взгляда внутри еще одной вызываемой ею функции) и при этом ошибочно использует ту же глобальнуюold_IFS
переменную для сохранения / восстановления, вы получаете ошибку.IFS
было не установлено, наивное сохранение и восстановление не будет работать, и даже приведет к прямым сбоям, если обычно (неправильно) используемаяset -u
(иначеset -o nounset
) опция оболочки в силе.help trap
). Если этот код также изменяет глобальный кодIFS
или предполагает, что он имеет определенное значение, вы можете получить незначительные ошибки.Вы могли бы разработать более надежную последовательность сохранения / восстановления (такую, как предложенная в этом другом ответе, чтобы избежать некоторых или всех этих проблем. Однако вам придется повторять этот фрагмент шумного стандартного кода везде, где вам временно нужен пользовательский
IFS
. уменьшает читабельность кода и ремонтопригодность.Дополнительные соображения для библиотечных сценариев
IFS
Это особенно беспокоит авторов библиотек функций оболочки, которым необходимо обеспечитьIFS
надежную работу своего кода независимо от глобального состояния ( параметры оболочки и т. д.), навязываемого их инициаторами, а также вообще не нарушая это состояние (инициаторы могут полагаться на нем всегда оставаться статичным).При написании кода библиотеки вы не можете полагаться на
IFS
какое-либо конкретное значение (даже не на значение по умолчанию) или даже на его установку вообще. Вместо этого вам нужно явно указатьIFS
любой фрагмент, от которого зависит его поведениеIFS
.Если
IFS
явно задано необходимое значение (даже если это значение по умолчанию) в каждой строке кода, где значение имеет значение, с использованием любого из двух механизмов, описанных в этом ответе, подходит для локализации эффекта, тогда код независимый от глобального государства и избегающий засорения его в целом. Этот подход имеет дополнительное преимущество, заключающееся в том, что он очень понятен для человека, читающего сценарий, которыйIFS
важен именно для этой одной команды / расширения с минимальными текстовыми затратами (по сравнению даже с самым простым сохранением / восстановлением).На какой код все равно влияет
IFS
?К счастью, не так много сценариев, в которых
IFS
имеет значение (при условии, что вы всегда цитируете свои расширения ):"$*"
и"${array[*]}"
расширенияread
встроенного таргетинга для нескольких переменных (read VAR1 VAR2 VAR3
) или переменной массива (read -a ARRAY_VAR_NAME
)read
таргетинга на одну переменную, когда дело доходит до начальных / конечных пробельных символов или непробельных символов, появляющихся вIFS
.источник
:
когда:
разделитель?:
это допустимый символ для использования в имени файла в большинстве файловых систем UNIX / Linux, поэтому вполне возможно иметь каталог с именем, содержащим:
. Возможно, в некоторых оболочках есть условие для выхода:
в PATH с использованием чего-то подобного\:
, и тогда вы увидите столбцы, которые не являются фактическими разделителями (кажется, что bash не допускает такое экранирование. Функция низкого уровня, используемая при итерации,$PATH
просто ищет:
в строка C: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
пример разделения:
более понятным.Зачем рисковать настройкой IFS,
$' \t\n'
когда все, что вам нужно сделать, этоКроме того, вы можете вызвать subshell, если вам не нужны переменные, установленные / измененные внутри:
источник
IFS
изначально было не установлено.