Читання рядків з файлу з bash: for vs. while


36

Я намагаюся прочитати текстовий файл і зробити щось із кожного рядка, використовуючи скрипт bash.

Отже, у мене є список, який виглядає так:

server1
server2
server3
server4

Я думав, що можу переосмислити це, використовуючи цикл час, так:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

Цикл while зупиняється після 1 запуску, таким чином працює лише uname -aна сервері1

Однак із циклом for для використання кішки це чудово працює:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

Для мене ще більше бентежить те, що це також працює:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

Чому мій перший приклад припиняється після першої ітерації?

Відповіді:


25

forПетля відмінно тут. Але зауважте, що це тому, що у файлі є назви машин, які не містять символів пробілу чи символів глобусу. for x in $(cat file); do …не працює з переглядом рядків fileзагалом, тому що оболонка спочатку розбиває вихід з команди cat fileде-небудь, де є пробіл, а потім розглядає кожне слово як глобульний візерунок, тому \[?*далі розширюється. Ви можете зробити for x in $(cat file)безпеку, якщо працюєте над цим:

set -f
IFS='
'
for x in $(cat file); do 

Пов'язане читання: Прокручування файлів із пробілами в іменах? ; Як я можу читати рядок за рядком зі змінної в bash? ; Чому while IFS= readвикористовується так часто, а не IFS=; while read..? Зауважте, що при використанні while readбезпечним синтаксисом для читання рядків є while IFS= read -r line; do ….

Тепер перейдемо до того, що піде не так у вашій while readспробі. Перенаправлення зі списку файлів серверів стосується всього циклу. Отже, коли він sshпрацює, його стандартний вхід надходить із цього файлу. Ssh-клієнт не може знати, коли віддалений додаток може прочитати зі свого стандартного входу. Отже, як тільки ssh-клієнт помітить деякий вхід, він надсилає цей вхід на віддалену сторону. Сервер ssh тоді готовий подати цей вхід у віддалену команду, якщо він цього хоче. У вашому випадку віддалена команда ніколи не читає жодного вводу, тому дані в кінцевому підсумку відкидаються, але сторона клієнта нічого про це не знає. Ваша спроба echoспрацьовано, тому що echoніколи не читає жодного вводу, він залишає свій стандартний вхід сам.

Є кілька способів уникнути цього. Ви можете сказати ssh не читати зі стандартного вводу, з -nопцією.

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

Фактично -nопція говорить sshпро переадресацію свого вводу з /dev/null. Ви можете зробити це на рівні оболонки, і він буде працювати для будь-якої команди.

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

Спосіб заманливо входу у уникнути SSH пришестя з файлу поставити переадресацію на readкоманді: while read server </home/kenny/list_of_servers.txt; do …. Це не буде працювати, оскільки це призводить до того, що файл буде відкриватися знову щоразу, коли readкоманда виконується (щоб він читав перший рядок файлу знову і знову). Перенаправлення повинно бути цілим циклом while, щоб файл відкривався один раз протягом тривалості циклу.

Загальне рішення полягає в наданні вводу в цикл дескриптора файлу, відмінного від стандартного вводу. Оболонка має конструкції для перевезення вводу і виведення з одного номера дескриптора на інший. Тут ми відкриваємо файл на дескрипторі файлу 3 і перенаправляємо readстандартний вхід команди з дескриптора файлу 3. Клієнт ssh ігнорує відкриті нестандартні дескриптори, тому все добре.

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

У bash readкоманда має специфічну опцію для читання з іншого дескриптора файлу, тому ви можете писати read -u3 server.

Пов'язане читання: Дескриптори файлів та сценарії оболонки ; Коли ви використовуєте додатковий дескриптор файлу?


5
Людина, ця зміна stackexchange відрізняється від стандартної за замовчуванням. Тут люди справді виходять із шляху, щоб не лише відповідати, а й навчати. Дуже дякую.
Кенні Расшаерт

26

Ви повинні використовувати while, неfor . Спосіб уникнути проковтування команд стандартного вводу в такій циклі - це просто використовувати інший дескриптор файлу :

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

Для отримання додаткової інформації help [r]ead( справді ) та іншої статті, яка пояснює, чому .


1
Цікаво, що я ніколи раніше не використовував дескриптори файлів. Що робить -uпрапор? Я не впевнений, на якій людині сторінки я повинен це шукати.
Кенні Расшаерт

Команда read є частиною обшивки оболонки, тому вона знаходиться на сторінці man в базі розділу "SHELL BUILTIN COMMANDS" у прочитаному абзаці. Ви знайдете "-u fd Прочитати введення з дескриптора файлу fd." у цьому абзаці
f4m8

6
Як варіант help read- helpце команда отримати еквівалент manсторінок для вбудованих оболонок.
l0b0

22

У вашому першому коді sshбуде "вкрасти" STDIN з while. Додайте -nваріант, sshщоб уникнути цього. Від man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).

Гаразд, маєте ідею, чому цього не відбувається з циклом for?
Кенні Расшаерт

7
Тому що forцикл отримує дані як параметр, а не як вхідні дані.
манастирство

1
Ви, пане, пан і науковець.
Кенні Расшаерт

0

Стандартна обробка вводу за замовчуванням sshзливає решту рядка з циклу while.

Щоб уникнути цієї проблеми, змініть, де проблемна команда читає стандартний вхід з. Якщо в команду не потрібно передавати стандартний ввід, прочитайте стандартний ввід зі спеціального /dev/nullпристрою.

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