Переменная, измененная внутри цикла while, не запоминается
В следующей программе, если я устанавливаю переменную $foo
в значение 1 внутри первого оператора if
, он работает в том смысле, что его значение запоминается после оператора if. Однако, когда я устанавливаю одну и ту же переменную в значение 2 внутри if
, которое находится внутри оператора while
, оно забывается после цикла while
. Он работает так, как будто я использую какую-то копию переменной $foo
внутри цикла while
, и я изменяю только эту конкретную копию. Здесь полная тестовая программа:
#!/bin/bash
set -e
set -u
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting \$foo to 1: $foo"
fi
echo "Variable \$foo after if statement: $foo"
lines="first line\nsecond line\nthird line"
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable \$foo updated to $foo inside if inside while loop"
fi
echo "Value of \$foo in while loop body: $foo"
done
echo "Variable \$foo after while loop: $foo"
# Output:
# $ ./testbash.sh
# Setting $foo to 1: 1
# Variable $foo after if statement: 1
# Value of $foo in while loop body: 1
# Variable $foo updated to 2 inside if inside while loop
# Value of $foo in while loop body: 2
# Value of $foo in while loop body: 2
# Variable $foo after while loop: 1
# bash --version
# GNU bash, version 4.1.10(4)-release (i686-pc-cygwin)
Ответы
Ответ 1
echo -e $lines | while read line
...
done
Цикл while
выполняется в подоболочке. Поэтому любые изменения, внесенные в переменную, будут недоступны после выхода из подоболочки.
Вместо этого вы можете использовать здесь строку, чтобы переписать цикл while для включения в основной процесс оболочки; только echo -e $lines
будет работать в подоболочке:
while read line
do
if [[ "$line" == "second line" ]]
then
foo=2
echo "Variable \$foo updated to $foo inside if inside while loop"
fi
echo "Value of \$foo in while loop body: $foo"
done <<< "$(echo -e "$lines")"
Вы можете избавиться от довольно некрасивого echo
в приведенной выше строке, расширив последовательности обратной косой черты сразу при назначении lines
. Здесь можно использовать форму цитирования $'...'
:
lines=$'first line\nsecond line\nthird line'
while read line; do
...
done <<< "$lines"
Ответ 2
ОБНОВЛЕНИЕ # 2
Объяснение в ответе Blue Moons.
Альтернативные решения:
Устранить echo
while read line; do
...
done <<EOT
first line
second line
third line
EOT
Добавить эхо внутри документа
while read line; do
...
done <<EOT
$(echo -e $lines)
EOT
Запустите echo
в фоновом режиме:
coproc echo -e $lines
while read -u ${COPROC[0]} line; do
...
done
Перенаправить в дескриптор файла явно (запомните пробел в < <
!):
exec 3< <(echo -e $lines)
while read -u 3 line; do
...
done
Или просто перенаправить на stdin
:
while read line; do
...
done < <(echo -e $lines)
И один для chepner
(исключая echo
):
arr=("first line" "second line" "third line");
for((i=0;i<${#arr[*]};++i)) { line=${arr[i]};
...
}
Переменная $lines
может быть преобразована в массив без запуска новой под-оболочки. Символы \
и n
должны быть преобразованы в некоторый символ (например, настоящий новый символ строки) и использовать переменную IFS (Internal Field Separator) для разделения строки на элементы массива. Это можно сделать следующим образом:
lines="first line\nsecond line\nthird line"
echo "$lines"
OIFS="$IFS"
IFS=$'\n' arr=(${lines//\\n/$'\n'}) # Conversion
IFS="$OIFS"
echo "${arr[@]}", Length: ${#arr[*]}
set|grep ^arr
Результат
first line\nsecond line\nthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")
Ответ 3
Вы пользователь 742342nd, чтобы задать этот bash FAQ. Ответ также описывает общий случай переменных, заданных в подоболочках, созданных трубами
E4) Если я передам вывод команды в read variable
, почему не выводится ли выход в $variable
, когда команда чтения заканчивается?
Это связано с отношениями родитель-потомок между Unix процессы. Он затрагивает все команды, выполняемые в конвейерах, а не только простые вызовы read
. Например, выдает команду вывод команды в цикл while
, который повторно вызывает read
, приведет к такое же поведение.
Каждый элемент конвейера, даже встроенная или оболочечная функция, выполняется в отдельном процессе, дочерний элемент оболочки, трубопровод. Подпроцесс не может повлиять на родительскую среду. Когда команда read
устанавливает переменную на вход, это переменная задается только в подоболочке, а не в родительской оболочке. когда подоболочка выходит, значение переменной теряется.
Многие конвейеры, которые заканчиваются на read variable
, могут быть преобразованы в подстановки команд, которые будут отображать вывод указанная команда. Затем вывод может быть назначен переменная:
grep ^gnu /usr/lib/news/active | wc -l | read ngroup
можно преобразовать в
ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)
Это, к сожалению, не работает, чтобы разделить текст среди множественные переменные, как читается, когда задано несколько переменных аргументы. Если вам нужно это сделать, вы можете использовать подстановка команды выше, чтобы прочитать вывод в переменную и вырезать переменную с помощью удаления шаблона bashоператоров расширения или использовать некоторые варианты следующих Подход.
Say/usr/local/bin/ipaddr - это следующая оболочка script:
#! /bin/sh
host `hostname` | awk '/address/ {print $NF}'
Вместо использования
/usr/local/bin/ipaddr | read A B C D
чтобы разбить IP-адрес локальной машины на отдельные октеты, используйте
OIFS="$IFS"
IFS=.
set -- $(/usr/local/bin/ipaddr)
IFS="$OIFS"
A="$1" B="$2" C="$3" D="$4"
Остерегайтесь, однако, что это изменит позиционирование оболочки параметры. Если они вам нужны, вы должны их сэкономить, прежде чем делать это.
Это общий подход - в большинстве случаев вам не понадобится установите $IFS на другое значение.
Некоторые другие пользовательские альтернативы включают:
read A B C D << HERE
$(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE
и, когда имеется замена процесса,
read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
Ответ 4
Хммм... Я бы почти поклялся, что это сработало для оригинальной оболочки Bourne, но теперь у меня нет доступа к текущей копии, чтобы проверить.
Существует, однако, очень тривиальное решение проблемы.
Измените первую строку script на:
#!/bin/bash
к
#!/bin/ksh
Эт вуаля! Чтение в конце конвейера работает отлично, если у вас установлена оболочка Korn.
Ответ 5
Как насчет очень простого метода
+call your while loop in a function
- set your value inside (nonsense, but shows the example)
- return your value inside
+capture your value outside
+set outside
+display outside
#!/bin/bash
# set -e
# set -u
# No idea why you need this, not using here
foo=0
bar="hello"
if [[ "$bar" == "hello" ]]
then
foo=1
echo "Setting \$foo to $foo"
fi
echo "Variable \$foo after if statement: $foo"
lines="first line\nsecond line\nthird line"
function my_while_loop
{
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2; return 2;
echo "Variable \$foo updated to $foo inside if inside while loop"
fi
echo -e $lines | while read line
do
if [[ "$line" == "second line" ]]
then
foo=2;
echo "Variable \$foo updated to $foo inside if inside while loop"
return 2;
fi
# Code below won't be executed since we returned from function in 'if' statement
# We aready reported the $foo var beint set to 2 anyway
echo "Value of \$foo in while loop body: $foo"
done
}
my_while_loop; foo="$?"
echo "Variable \$foo after while loop: $foo"
Output:
Setting $foo 1
Variable $foo after if statement: 1
Value of $foo in while loop body: 1
Variable $foo after while loop: 2
bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
Copyright (C) 2007 Free Software Foundation, Inc.
Ответ 6
Это интересный вопрос, затрагивающий очень базовую концепцию оболочки Bourne и subshell. Здесь я предлагаю решение, которое отличается от предыдущих решений тем, что выполняет какую-то фильтрацию. Я приведу пример, который может быть полезен в реальной жизни. Это фрагмент для проверки того, что загруженные файлы соответствуют известной контрольной сумме. Файл контрольной суммы выглядит следующим образом (показывает всего 3 строки):
49174 36326 dna_align_feature.txt.gz
54757 1 dna.txt.gz
55409 9971 exon_transcript.txt.gz
Сценарий оболочки:
#!/bin/sh
.....
failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
num1=$(echo $line | awk '{print $1}')
num2=$(echo $line | awk '{print $2}')
fname=$(echo $line | awk '{print $3}')
if [ -f "$fname" ]; then
res=$(sum $fname)
filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
if [ "$filegood" = "FALSE" ]; then
failcnt=$(expr $failcnt + 1) # only in subshell
echo "$fname BAD $failcnt"
fi
fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
echo $failcnt files failed
else
echo download successful
fi
Родитель и subshell взаимодействуют с помощью команды echo. Вы можете выбрать несколько простых для анализа текста для родительской оболочки. Этот метод не нарушает ваш обычный образ мышления, просто вы должны выполнить некоторую постобработку. Для этого вы можете использовать grep, sed, awk и другие.