Понять «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 нужно было изменить где-то ранее в скрипте.

  • Как я могу исправить скрипт завершения табуляции имени хоста SSH?
  • Создание нескольких символических ссылок при сохранении структуры каталогов
  • Копирование нового файла и отправка по электронной почте
  • Максимальная длина символа для команды Read (ввод)
  • Как удалить двойные кавычки из файла, но не внутри двойных кавычек
  • Отправьте EOF на именованный канал - очистка / высыхание fifo
  • sh-скрипт, содержащий элемент perl, не производит такой же вывод через crontab как ручное выполнение
  • Перемещение подкаталога в новый родительский каталог, где новое имя каталога увеличивается на 1
  • Перенаправить ввод в несколько разных файлов вывода
  • Как удалить файл на удаленном компьютере через SSH с помощью сценария оболочки?
  • Оболочка сценария скрипта условно
  • Interesting Posts

    Вызывать команду / сценарий, отключенный от управляющего терминала?

    Можно ли игнорировать скрытые файлы Windows?

    g ++ bash: warning: programmable_completion: g ++: возможный цикл повтора

    Настройка предупреждения, когда программа пытается взаимодействовать с пользователем

    Какова идея, по которой rm не удаляет файл без записи по умолчанию?

    Как переназначить («на программном уровне») клавиши Fn и Ctrl на ноутбуке Acer?

    Как изменить все строки в файле python от snake_case до camelCase в sed

    Что заставляет SSH прекратить работу по проводному соединению, когда я подключаю беспроводную карту USB?

    Как я могу использовать sed для замены многострочной строки?

    Отложите запуск некоторых демонов до тех пор, пока не загрузится диск при загрузке

    если условие с ssh выходит во время цикла без завершения

    Замена текста между двумя конкретными строками

    Shell Script: используйте «find» для запуска оболочки команду, содержащую «имя файла» (без расширения), возвращенную из «find»,

    Как «открыть» на «отфильтрованном» порту

    lsof и прослушивающие порты

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