Чому моя змінна локальна в одному циклі "while read", а не в іншому, здавалося б, подібному циклі?


25

Чому я отримую різні значення $xіз наведених нижче фрагментів?

#!/bin/bash

x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?

Аналогічна публікація у стеку переповнення: змінна, змінена у циклі часу, не запам'ятовується .
codeforester

Відповіді:


26

Правильне пояснення вже дали jsbillings та geekosaur , але дозвольте мені трохи розширити це.

У більшості оболонок, включаючи bash, кожна сторона трубопроводу проходить у нижній оболонці, тому будь-яка зміна внутрішнього стану оболонки (наприклад, встановлення змінних) залишається обмеженою саме тим сегментом трубопроводу. Єдина інформація, яку ви можете отримати з допоміжної оболонки, - це те, що вона виводить (стандартному виводу та іншим дескрипторам файлів) та його вихідний код (який становить число від 0 до 255). Наприклад, такі фрагменти друкує 0:

a=0; a=1 | a=2; echo $a

У ksh (варіанти, отримані з коду AT&T, а не pdksh / mksh варіанти) та zsh, останній елемент у конвеєрі виконується у батьківській оболонці. (POSIX дозволяє обидва способи поведінки.) Отже, фрагмент вище друкується 2.

Корисна ідіома полягає в тому, щоб включити продовження циклу while (або все, що у вас є, з правого боку трубопроводу, але цикл на деякий час тут насправді поширений) у трубопровід:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}

1
Дякую Жиллю .. Що a = 0; a = 1 | a = 2 дає дуже чітку картину .. і не тільки про локалізацію внутрішнього стану, але і про те, що трубопроводу насправді не потрібно нічого надсилати по трубі (крім коду виходу (?)). цікаве розуміння в трубу ... мені вдалося отримати мій сценарій працює з < <(locate -ber ^\.tag$), завдяки оригінальному злегка неясного відповіді і geekosaur і comemnts Glenn Джекман .. Я спочатку був у дилемі про прийняття відповіді, але результат сінгапурського було досить чітко, особливо з коментарями для подальшого спостереження за jsbillings :)
Peter.O

таке відчуття, ніби я перебрався у функцію, тому я перемістив деякі змінні та тести всередину неї, і вона працювала чудово, THX!
Сила Водолія

8

Ви стикаєтеся із проблемою із змінною сферою. Змінні, визначені в циклі while, який знаходиться в правій частині труби, мають свій власний контекст локальної області, і зміни змінної не будуть видно за межами циклу. Цикл while - це, по суті, підрозділ, який отримує КОПІЮВАННЯ середовища оболонки, а будь-які зміни в середовищі втрачаються в кінці оболонки. Дивіться це питання StackOverflow .

ОНОВЛЕНО : Я знехтував вказати на важливий факт, що цикл у той час, як його власна нижня оболонка, пояснюється тим, що він є кінцевою точкою труби, я оновив це у відповіді.


@jsbillings .. Гаразд, це пояснює два останні фрагменти, але це не пояснює перший, де значення $ x, встановлене в циклі, передається як 55 (за межами циклу "while")
Петро.О

5
@ fred.bear: Він працює в whileциклі як кінцевий кінець трубопроводу, який кидає його в нижню частину корпусу .
geekosaur

2
Ось тут починає грати заміна процесу баш. Замість цього blah|blah|while read ...ви можете матиwhile read ...; done < <(blah|blah)
glenn jackman

1
@geekosaur: дякую за заповнення деталей, які я знехтував включити у свою відповідь.
jsbillings

1
-1 Вибачте, але ця відповідь просто неправильна. Це пояснює, як цей матеріал працює у багатьох мовах програмування, але не в оболонці. @Gilles, внизу, правильно зрозумів.
jpc

6

Як вже згадувалося в інших відповідях , частини трубопроводу проходять в допоміжних оболонках, тому внесені там модифікації не видно для основної оболонки.

Якщо ми розглянемо лише Bash, крім cmd | { stuff; more stuff; }структури є ще два обхідні шляхи :

  1. Перенаправити вхід із заміни процесу :

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"

    Вихід з команди в in <(...)робиться таким, що видається так, ніби це була названа труба.

  2. lastpipeВаріант, який робить Bash роботу як KSH, і запускає останню частину трубопроводу в основному процесі оболонки. Хоча він працює лише в тому випадку, якщо контроль роботи відключений, тобто не в інтерактивній оболонці:

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

    або

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

Звичайно, підміна процесу підтримується в ksh і zsh. Але оскільки в останню чергу вони проходять останню частину трубопроводу в основній оболонці, використовувати його як спосіб вирішення не потрібно.


0
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

це може працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.