Понять «IFS = read -r line»?

Я, очевидно, понимаю, что можно добавить значение к внутренней переменной разделителя полей. Например:

$ IFS=blah $ echo "$IFS" blah $ 

Я также понимаю, что read -r line будет сохранять данные из stdin в переменную named line :

 $ read -r line <<< blah $ echo "$line" blah $ 

Однако как команда может назначить значение переменной? И он сначала хранит данные от stdin до переменной line а затем дает значение line IFS ?

3 Solutions collect form web for “Понять «IFS = read -r line»?”

У некоторых людей есть ошибочное представление о том, что read – это команда для чтения строки. Это не.

read читает слова из строки (возможно, обратной косой черты), где слова ограничены $IFS и обратная косая черта может использоваться для исключения разделителей (или продолжения строк).

Общий синтаксис:

 read word1 word2... remaining_words 

read читает stdin по одному байту за раз, пока не найдет символ незавершённой новой строки (или конец ввода), разделяет это согласно сложным правилам и сохраняет результат этого расщепления на $word1 , $word2$remaining_words $word2 .

Например, на входе, например:

  <tab> foo bar\ baz bl\ah blah\ whatever whatever 

и со значением по умолчанию $IFS , read abc присваивает:

  • $afoo
  • $bbar baz
  • $cblah blahwhatever whatever

Теперь, если передано только один аргумент, это не станет read line . Он по-прежнему read remaining_words . Обработка обратной косой черты по-прежнему выполняется, символы пробелов IFS по-прежнему удаляются с начала и конца.

Параметр -r удаляет обратную косую черту. Так что эта же команда выше с -r вместо этого назначит

  • $afoo
  • $bbar\
  • $cbaz bl\ah blah\

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

С IFS=: ( : не являясь символом пробела IFS), вход вроде :foo::bar:: был бы разделен на "" , "foo" , "" , bar и "" (и дополнительный "" с некоторыми однако это не имеет значения, кроме как read -a ). Если мы заменим это : с пространством, разделение выполняется только в foo и bar . То есть ведущие и конечные игнорируются, а последовательности из них рассматриваются как один. Существуют дополнительные правила, когда пробельные и небелые символы объединены в $IFS . Некоторые реализации могут добавлять / удалять специальную обработку, удваивая символы в IFS ( IFS=:: или IFS=' ' ).

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

Даже с символами IFS-non-whitespace, если строка ввода содержит один (и только один) этих символов, и это последний символ в строке (например, IFS=: read -r word на входе, например foo: с оболочками POSIX (не zsh и некоторые версии pdksh ), этот вход считается одним словом foo потому что в этих оболочках символы $IFS считаются терминаторами , поэтому word будет содержать foo , а не foo:

Итак, канонический способ чтения одной строки ввода с помощью встроенного read :

 IFS= read -r line 

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

Использование синтаксиса var=value cmd гарантирует, что IFS устанавливается только по-разному в течение всей команды cmd .

Заметка истории

read встроенного было введено оболочкой Борна и уже было читать слова , а не линии. Есть несколько важных отличий от современных оболочек POSIX.

Оболочка оболочки Bourne не поддерживала параметр -r (который был введен оболочкой Korn), поэтому нет возможности отключить обработку обратной косой черты, отличную от предварительной обработки ввода, с помощью команды sed 's/\\/&&/g' там.

У оболочки Борна не было такого понятия двух классов символов (которое снова было введено ksh). В оболочке Bourne все символы подвергаются тому же обращению, что и символы пробелов IFS в ksh, то есть IFS=: read abc на входе, например foo::bar , присваивает bar значение $b , а не пустую строку.

В оболочке Bourne:

 var=value cmd 

Если cmd является встроенным (например, read is), var остается установленным в value после завершения cmd . Это особенно важно для $IFS потому что в оболочке Bourne $IFS используется для разделения всего, а не только на разложения. Кроме того, если вы удалите символ пробела из $IFS в оболочке Bourne, "$@" больше не работает.

В оболочке Bourne перенаправление составной команды заставляет ее работать в подоболочке (в ранних версиях даже такие вещи, как read var < file или exec 3< file; read var <&3 не работало), поэтому это было редко встречается в оболочка Bourne, чтобы использовать read для чего угодно, кроме ввода пользователем на терминале (где эта обработка продолжения строки имела смысл)

Некоторые Unices (например, HP / UX, есть еще один в util-linux ), все еще имеют командную line для чтения одной строки ввода (которая раньше была стандартной командой Unix до версии Single Unix версии 2 ).

Это в основном то же самое, что и head -n 1 за исключением того, что он читает по одному байту за раз, чтобы убедиться, что он не читает больше одной строки. В этих системах вы можете:

 line=`line` 

Конечно, это означает, что вы создаете новый процесс, выполняете команду и читаете ее вывод через канал, поэтому намного менее эффективны, чем IFS= read -r line ksh, но все же гораздо более интуитивно понятная.

Вы должны прочитать этот оператор в двух частях, первый очистит значение переменной IFS, т. Е. Эквивалентен более читаемому IFS="" , второй – считывает переменную line из stdin, read -r line .

Специфика этого синтаксиса заключается в том, что IFS-аффектация является транзитивной и действительна только для команды read .

Если я что-то не хватает, в этом конкретном случае очистка IFS имеет никакого эффекта, хотя, как и любой IFS , вся строка будет считана в переменной line . Было бы изменение поведения только в том случае, если в качестве параметра для команды read было передано более одной переменной.

Редактировать:

Параметр -r позволяет разрешить ввод, заканчивающийся \ не обрабатываться специально, то есть для обратной косой черты, которая должна быть включена в переменную line а не как символ продолжения, чтобы разрешить многострочный ввод.

 $ read line; echo "[$line]" abc\ > def [abcdef] $ read -r line; echo "[$line]" abc\ [abc\] 

Очистка IFS имеет побочный эффект предотвращения чтения для обрезки потенциальных ведущих и конечных пробелов или символов табуляции, например:

 $ echo " abc " | { IFS= read -r line; echo "[$line]" ; } [ abc ] $ echo " abc " | { read -r line; echo "[$line]" ; } [abc] 

Благодаря rici для указания этой разницы.

Теория

Здесь есть две концепции:

  • IFS – это разделитель входных полей, что означает, что чтение строки будет разделено на основе символов IFS . В командной строке IFS обычно представляет собой пробельные символы, поэтому командная строка разбивается на пробелы.
  • Выполнение чего-то вроде команды VAR=value command означает «изменить среду команды, чтобы значение VAR имело value ». В основном команда command увидит, что VAR имеет значение value , но любая команда, выполненная после этого, по-прежнему будет видеть, что VAR имеет свое предыдущее значение. Другими словами, эта переменная будет изменена только для этого утверждения.

В этом случае

Поэтому, когда вы выполняете IFS= read -r line , то, что вы делаете, устанавливает IFS в пустую строку (никакой символ не будет использоваться для разделения, поэтому не будет разбиения), чтобы read читало всю строку и рассматривало ее как одно слово который будет присвоен переменной line . Изменения в IFS влияют только на этот оператор, так что изменения не будут затронуты никакими последующими командами.

Как примечание

Хотя команда правильная и будет работать по назначению, установка IFS в этом случае не может быть 1 не нужна. Как написано на странице bash man в read секции встроенного:

Одна строка считывается со стандартного ввода […], и первое слово присваивается первому имени, второму слову второму имени и т. Д., С остальными словами и их промежуточными разделителями, назначенными на фамилию . Если из входного потока меньше слов, чем имен, оставшимся именам присваиваются пустые значения. Символы в IFS используются для разделения строки на слова. […]

Поскольку у вас есть только переменная line , все слова будут назначены ей в любом случае, поэтому, если вам не нужны никакие предыдущие и конечные символы пробела 1, вы можете просто написать read -r line и сделать с ней.

[1] Как пример того, как значение unset или default $IFS приведет к тому, что read будет считаться с пробелом IFS , вы можете попробовать:

 echo ' where are my spaces? ' | { unset IFS read -r line printf %s\\n "$line" } | sed -nl 

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

  • Лучший способ обфускать сценарий AIX
  • Почему цикл while пропускает и читает только первую строку?
  • удалить строки, где значение поля меньше или равно 3 - sed или awk?
  • Печать полей с помощью awk
  • Как отсортировать файлы в Bash с помощью ddmmyy timestamps в имени
  • Сохранять коды выхода при захвате SIGINT и тому подобное?
  • Можно ли написать в crontab из многоцелевого скрипта?
  • Создание туннеля openvpn в сценарии bash
  • Выровнять текст в центр с заполнением с обеих сторон
  • Переменная как команда; eval vs bash -c
  • Как создать новый пустой файл в сценарии bash?
  • Interesting Posts

    Как выполнить 'find' с 'sed' в функции bash

    Как удалить прежний каталог MTP, который теперь дает мне ошибку ввода / вывода всякий раз, когда я пытаюсь?

    Debian apt не может найти пакет cairo-dock, только на ноутбуке

    Как настроить движение для доступа к моему скопированному файлу конфигурации?

    Извлечение значений из HTML через парсер HTML

    Что такое wget-идиома для curl -o?

    Отсутствует отдельный debuginfo для – GDB

    grep, чтобы найти "print" не "#print"

    Какой инструмент я могу использовать для обнюхивания трафика HTTP / HTTPS?

    Лучше использовать !! или история?

    Скопируйте строку в текстовый файл и добавьте измененную строку в конец строки в том же файле.

    Преобразование чисел json в строки в оболочке

    pkg-config не распознает какие-либо опции по какой-либо причине

    Как перенаправить / переправить между устройством tun / tap и сокетом с помощью Linux-инструментов?

    Как настроить набор компьютеров для netboot?

    Linux и Unix - лучшая ОС в мире.