В то время как IFS = read..`, почему IFS не имеет никакого эффекта?

12

Возможно, у меня что-то не так, но мне кажется убедительным, что установка IFS в качестве одной из команд в списке pre-do / done абсолютно не влияет.
Внешний IFS (вне whileконструкции) преобладает во всех примерах, показанных в сценарии ниже.

Что тут происходит? Я неправильно понял, что IFS делает в этой ситуации? Я ожидал, что результаты разбиения массива будут такими, как показано в столбце «ожидаемый».


#!/bin/bash
xifs() { echo -n "$(echo -n "$IFS" | xxd -p)"; } # allow for null $IFS 
show() { x=($1) 
         echo -ne "  (${#x[@]})\t |"
         for ((j=0;j<${#x[@]};j++)); do 
           echo -n "${x[j]}|"
         done
         echo -ne "\t"
         xifs "$IFS"; echo
}
data="a  b   c"
echo -e "-----   --  -- \t --------\tactual"
echo -e "outside        \t  IFS    \tinside" 
echo -e "loop           \t Field   \tloop" 
echo -e "IFS     NR  NF \t Split   \tIFS (actual)" 
echo -e "-----   --  -- \t --------\t-----"
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 1'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 2'; show "$REPLY"; done 
IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 3'; show "$REPLY"; done
IFS=" ";      xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 4'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t 5'; show "$REPLY"; done 
IFS=" ";      xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 6'; show "$REPLY"; done
IFS=;         xifs "$IFS"; echo "$data" | while         read; do echo -ne '\t 7'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t 8'; show "$REPLY"; done 
IFS=;         xifs "$IFS"; echo "$data" | while IFS=b   read; do echo -ne '\t 9'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=    read; do echo -ne '\t10'; show "$REPLY"; done
IFS=b;        xifs "$IFS"; echo "$data" | while IFS=" " read; do echo -ne '\t11'; show "$REPLY"; done
echo -e "-----   --  -- \t --------\t-----"

Выход:

-----   --  --   --------       actual   
outside           IFS           inside                assigned   
loop             Field          loop    #              inner
IFS     NR  NF   Split          IFS     #  expected    IFS
-----   --  --   --------       -----   #  ---------  --------
20090a   1  (3)  |a|b|c|        20090a  #                              
20090a   2  (3)  |a|b|c|        20090a  #  |a  b   c|  IFS=
20090a   3  (3)  |a|b|c|        20090a  #  |a  |   c|  IFS=b
20       4  (3)  |a|b|c|        20      #                          
20       5  (3)  |a|b|c|        20      #  |a  b   c   IFS=
20       6  (3)  |a|b|c|        20      #  |a  |   c|  IFS=b
         7  (1)  |a  b   c|             #                          
         8  (1)  |a  b   c|             #  |a|b|c|     IFS=" "
         9  (1)  |a  b   c|             #  |a  |   c|  IFS=b
62      10  (2)  |a  |   c|     62      #  |a  b   c|  IFS=
62      11  (2)  |a  |   c|     62      #  |a|b|c|     IFS=" "
-----   --  --   --------       -----      ---------   -------                        
Peter.O
источник

Ответы:

17

(Извините, длинное объяснение)

Да, IFSпеременная in while IFS=" " read; do …не влияет на остальную часть кода.

Давайте сначала уточним, что командная строка оболочки содержит два разных типа переменных:

  • переменные оболочки (которые существуют только внутри оболочки и являются локальными для оболочки)
  • переменные среды, которые существуют для каждого процесса. Они обычно сохраняются fork()и exec(), таким образом, дочерние процессы наследуют их.

Когда вы вызываете команду с помощью:

  A=foo B=bar command

команда выполняется в среде, в которой переменная (среда) Aустановлена fooи Bимеет значение bar. Но с помощью этой командной строки текущие переменные оболочки Aи Bостаются неизменными .

Это отличается от:

A=foo; B=bar; command

Здесь переменные оболочки Aи Bопределены, а команда запускается без переменных среды Aи Bопределяется. Ценности Aи Bнедоступны из command.

Однако, если некоторые переменные оболочки имеют export-ed, соответствующие переменные среды синхронизируются с соответствующими им переменными оболочки. Пример:

export A
export B
A=foo; B=bar; command

С помощью этого кода, обе оболочки переменных и оболочки среды переменных устанавливаются fooи bar. Поскольку переменные среды наследуются подпроцессами, commandони смогут получить доступ к их значениям.

Чтобы вернуться к исходному вопросу, в:

IFS='a' read

readвлияет только . И на самом деле, в этом случае, readне волнует значение IFSпеременной. Используется IFSтолько когда вы просите разделить строку (и сохранить в нескольких переменных), как в:

echo "a :  b :    c" | IFS=":" read i j k; \
    printf "i is '%s', j is '%s', k is '%s'" "$i" "$j" "$k"

IFSне используется, readесли не вызывается с аргументами. ( Редактировать: это не совсем так: пробельные символы, то есть пробел и табуляция, присутствующие в IFS, всегда игнорируются в начале / конце строки ввода.)

Стефан Хименес
источник
Какое великолепное объяснение! Это так просто! Я был ошеломлен этим синтаксисом «без точки с запятой» в течение нескольких месяцев; и это просто случай того, что это означает локальную переменную! .. rozcietrzewiacz открыл для меня путь (большой) в другом вопросе ... и вы только что положили глазурь на торт ... Я был всю ночь на этом, и это, конечно, стоило того для таких хороших и ясных ответов! .. Спасибо ..
Peter.O
Эмм. Мне пришлось прочитать этот комментарий для редактирования несколько раз, прежде чем я его получил - вы хотите сказать, что пробельные символы, которые присутствуют $IFS, удаляются в начале / конце строки ввода, я полагаю? (
Вот
Пожалуйста , обратите внимание на это: unix.stackexchange.com/questions/382963/...
Значение КСФ является важным даже при чтении одной переменной, потому что оболочка по- прежнему делает слово расщепление на входе. Так, например, ввод символов a<tab>bв read varприведет к тому, что var будет иметь значение a<space>b, но если вместо этого у вас будет IFS='<newline>' read varзначение var a<tab>b.
Джон Хэскол
8

Проще говоря, вы должны читать более чем одну переменную за раз, чтобы IFS=<something> read ...конструкция имела видимый эффект в ваших примерах 1 .

Вы упускаете сферу действия readв примерах. В ваших тестовых примерах нет модификации IFS внутри цикла. Позвольте мне точно указать, где второй IFS влияет на каждую из ваших строк:

 IFS=$' \t\n'; xifs "$IFS"; echo "$data" | while IFS=b   read; do echo ...
                                                      ^      ^
                                                      |      |
                                          from here --'       `- to here :)

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

 $ data="a  b   c"
 $ echo "$data" | while           read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a|b||c|
 $ echo "$data" | while IFS=      read A B C; do echo \|$A\|$B\|\|$C\|; done
 |a b c||||
 $ echo "$data" | while IFS='a'   read A B C; do echo \|$A\|$B\|\|$C\|; done
 || b c|||
 $ echo "$data" | while IFS='ab'  read A B C; do echo \|$A\|$B\|\|$C\|; done
 || || c|

1 Как я только что узнал от Жиля , на самом деле может быть преимущество установки IFS=''(пробела) при чтении только одного поля: это позволяет избежать усечения пробела в начале строки.

rozcietrzewiacz
источник
Хорошо .. Спасибо ... Я получил это на этот раз .. и я люблю твой эскиз :)
Peter.O
Хорошо, теперь я прочитал ваш комментарий о том, что вы не заметили мой ответ на этот вопрос в другом вопросе. Может быть, вы могли бы просто отменить другой и удалить это, так как это действительно одна общая проблема?
rozcietrzewiacz
Да, у этих двух вопросов есть связанная тема, но заголовок другого - «Почему IFS= readиспользуется вместо простой переустановки переменной среды IFS». Я не знал, что локальные переменные могут быть установлены вызывающей командой. Это был ответ на этот вопрос. Это дало дальнейшее развитие к решению основного вопроса этого вопроса, но к тому времени, когда я понял это, я уже задавал этот вопрос ... Возможно, эти два вопроса похожи на два sedвопроса, так что я чувствую, что это так ... Другие названия для Google для Google.
Peter.O