Любопытная проблема: для циклов `` vs. while-read и видимости переменных в скриптах оболочки - PullRequest
1 голос
/ 25 мая 2011

У меня проблема с обновлением значения переменной в скрипте оболочки из цикла while. Это можно смоделировать с помощью следующего фрагмента кода:

 printf "aaa\nbbb\n" | \
      while read x ; do
          y=$x
          echo "INSIDE: $y"
      done
 echo "OUTSIDE: $y"

Выход:

INSIDE: aaa
INSIDE: bbb
OUTSIDE: 

Здесь команда printf просто отображает две строки, в то время как цикл чтения читает построчно, обновляя определенную переменную, но как только управление выходит из цикла, значение переменной теряется.

Я полагаю, что проблема связана с тем фактом, что оператор pipe-while-read заставляет shell выполнять тело цикла в подпроцессе, который не может обновить переменные оболочки в основном цикле.

Если я перепишу первые две строки кода как

 for x in `printf "aaa\nbbb\n" ` ; do

Выход:

INSIDE: aaa
INSIDE: bbb
OUTSIDE: bbb

Это может быть обходной путь, но не для моего случая, потому что на самом деле у меня есть не 'aaa' и 'bbb', а более сложные строки, включая пробелы и т. Д.

Есть идеи, как решить эту проблему, а именно: читать строку команды за строкой в ​​цикле и иметь возможность обновлять переменные оболочки?

Спасибо.

Ответы [ 2 ]

1 голос
/ 25 мая 2011

Выдержка из man bash:

Каждая команда в конвейере выполняется как отдельный процесс (т. Е. В подоболочке).

И Subshell не может изменить переменную в Parent.

Одно из возможных решений:

IFS='\n'
while read x ; do
   y=${x}
   echo "INSIDE: ${y}"
done <<EOT
aaa
bbb
EOT
echo "OUTSIDE: ${y}"

Или, если ввод является файлом:

IFS='\n'
while read x ; do
   y=${x}
   echo "INSIDE: ${y}"
done < /path/to/file
echo "OUTSIDE: ${y}"

Это читает одну строку за раз, и не имеет никаких проблем с пробелами.

1 голос
/ 25 мая 2011

Вы можете избавиться от конвейера, используя подстановка процесса вместо:

while read x ; do
    y=$x
    echo "INSIDE: $y"
done < <(printf "aaa\nbbb\n")
echo "OUTSIDE: $y"

В качестве альтернативы, если ваш ввод находится в файле, вы можете перенаправить его впока:

while read x ; do
    y=$x
    echo "INSIDE: $y"
done < file
echo "OUTSIDE: $y"
...