bash: змінна втрачає значення наприкінці циклу читання


36

У мене є проблема в одному зі моїх скриптів оболонки. Запитав у кількох колег, але всі вони просто хитають головою (після деяких подряпин), тож я прийшов сюди для відповіді.

Наскільки я розумію, наступний скрипт оболонки повинен друкувати "Count is 5" як останній рядок. За винятком цього немає. На ній друкується "Кількість 0". Якщо "поки читається" замінено будь-яким іншим циклом, це спрацює чудово. Ось сценарій:

echo "1"> input.data
ехо "2" >> вхідні дані
відлуння "3" >> input.data
ехо "4" >> вхідні дані
ехо "5" >> вхідні дані

CNT = 0 

cat input.data | під час читання;
робити
  нехай CNT ++;
  відлуння "Відлік до $ CNT"
зроблено 
ехо "Кількість - $ CNT"

Чому це відбувається і як я можу це запобігти? Я спробував це в Debian Lenny та Squeeze, такий же результат (тобто bash 3.2.39 і bash 4.1.5. Я повністю визнаю, що не є майстром сценарію оболонки, тому будь-які вказівники будуть вдячні.

Відповіді:


30

Дивіться аргумент @ Bash, запитання № 24: "Я встановлюю змінні в циклі. Чому вони раптово зникають після закінчення циклу? Або чому я не можу передавати дані для читання?" (останнім часом тут архівовано ).

Підсумок: Це підтримується лише з bash 4.2 і вище. Якщо ви використовуєте bash, вам потрібно використовувати різні способи, такі як підстановка команд замість pipe.


Ви отримуєте бонус, оскільки ваша відповідь надала мені найширший спектр варіантів.
wolfgangsz

5
Посилання мертва. Ось чому відповіді лише на посилання погані. Принаймні, підсумуйте відповідь тут.
rudolfbyker

Боже, але інший раз, коли ksh просто набагато краще ... чому, тільки чому всі стікалися навколо баш.
Флоріан Хейгл

@FlorianHeigl: Ви стверджуєте, що ksh - Єдина справжня оболонка?
Ігнасіо Васкес-Абрамс

@ IgnacioVazquez-Abrams ні, але я стверджую, що обробка петлі в bash - це жахливо PITA. Цифрова обробка була довгою бігункою, яка не вдавалася до 1993 року. Інші речі - це обробка в режимі getopt, де (також 1993 р.) Вбудований обробник був простим і здатним, чого ви все одно не можете отримати, якщо не використовувати ie docopt. Я стверджую, що Баш вже більше 20 років наполегливо ставив себе за криву, а кількість часу, витрачене на ЦЕ ТУТ, або мільйони поганих вживань, не вживається - приймається лише тому, що більшість людей ніколи не дізнається.
Флоріан Хейгл

30

Це своєрідна помилка. Труби створюють SubShells, тому while readзапуск працює на іншій оболонці, ніж ваш сценарій, завдяки чому ваша CNTзмінна ніколи не змінюється (лише та, яка знаходиться всередині підрозділу труби).

Згрупуйте останнє echoза допомогою допоміжної оболонки, whileщоб виправити її (існує багато інших способів її виправити. Це один. Відповіді Іена та Ігнасіо мають інші.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Довге пояснення:

  1. Ви заявляєте CNTу своєму сценарії значення 0;
  2. SubShell запускається на |до while read;
  3. Ваша $CNTзмінна експортується в SubShell зі значенням 0;
  4. SubShell рахує і збільшує CNTзначення до 5;
  5. Закінчення SubShell, змінні та значення знищуються (вони не повертаються до процесу виклику / сценарію).
  6. Ви echoпочаткове CNTзначення 0.

2
Перший сценарій оболонки, який я коли-небудь писав, дав мені ті ж самі питання, якийсь час ударив головою об стіну, перш ніж з'ясувати, що ці труби породили додаткові снаряди. Будь-яка змінна, з якою ви заплутуєтесь у трубі, вийде із сфери дії, як тільки труба закінчиться - це означає, що якщо ви дійсно дуже хочете зробити щось зі змінною за межами труби, в якій вона використовується, вам доведеться утримуйте стан через щось фанкі, як тимчасовий файл.
фотоіонізоване

Відмінна відповідь, на жаль, я можу дати лише один бонус за прийняття. Вибачте.
wolfgangsz

10

Це працює

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

Мені подобається, розумний спосіб, тому що ви знаєте, де потрібні дані, і потрібно лише їх повернути. Якщо ви не знаєте рішень високої майстерності, ви завжди можете "прочитати файл" ха-ха-ха. +1 для вас.
м3нда

1
Хто читає це, пам’ятайте, що рішення, яке надає Iain, працює лише тоді, коли у вас сценарій явно викликає bash, маючи перший рядок: #! / Bin / bash і що:!! / Bin / sh не буде працювати.
Roadowl

1
Цікавий перший приклад, який я коли-небудь бачив, де безкорисне використання Cat фактично заважало коду працювати . До речі, @Roadowl, єдиний башизм тут - це лінія, let CNT++яка повинна використовуватись CNT="$((CNT+1))"для використання арифметичної розширення, сумісної з POSIX . Решта вона вже портативна.
Wildcard

6

Спробуйте передати дані замість оболонки, як-от файл перед циклом while. Це схоже на рішення Lain, але передбачається, що ви не хочете переривчастого файлу:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.