Прочитайте рядок файлів за рядком, присвоївши значення змінній


752

У мене є такий файл .txt:

Marco
Paolo
Antonio

Я хочу прочитати його по черзі, і для кожного рядка я хочу призначити змінній значення .txt. Припустимо, моя змінна $name: потік:

  • Прочитайте перший рядок із файлу
  • Assign $name= "Марко"
  • Виконайте кілька завдань із $name
  • Прочитайте другий рядок з файлу
  • Assign $name= "Паоло"


3
Чи можна ці питання об'єднати якось? В обох є кілька справді хороших відповідей, які висвітлюють різні аспекти проблеми, погані відповіді мають поглиблені пояснення в коментарях, що в них погано, і на сьогоднішній день ви не можете отримати цілий огляд того, що слід врахувати, з відповідей одне єдине запитання від пари. Було б корисно мати все це в одному місці, а не розбивати на 2 сторінки.
Егор Ганс

Відповіді:


1356

Далі читається файл, переданий як аргумент за рядком:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Це стандартна форма для читання рядків з файлу в циклі. Пояснення:

  • IFS=(або IFS='') запобігає обробці пробілу / заднім пробілом.
  • -r запобігає інтерпретації втечі зворотнього косого кута.

Або ви можете помістити його в допоміжний скрипт файлу bash, наприклад, вміст:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Якщо вищезазначене збережено у сценарії з назвою файлу readfile, його можна виконати наступним чином:

chmod +x readfile
./readfile filename.txt

Якщо файл не є стандартним текстовим файлом POSIX (= не закінчується символом нового рядка), цикл може бути модифікований, щоб обробляти тривалі часткові рядки:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Ось || [[ -n $line ]] запобігає ігноруванню останнього рядка, якщо він не закінчується символом \n(оскільки readповертає ненульовий код виходу, коли він стикається з EOF).

Якщо команди, що знаходяться в циклі, також читаються зі стандартного введення, дескриптор файлу, який використовується, readможна змінити на щось інше (уникайте стандартних дескрипторів файлів ), наприклад:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Оболонки, що не мають башів, можуть не знати read -u3; використовуйте read <&3замість цього.)


23
Існує застереження з цим методом. Якщо всередині циклу while є що-небудь інтерактивне (наприклад, читання з stdin), то він буде брати свій внесок від $ 1. Вам не дадуть шанс ввести дані вручну.
carpie

10
Зверніть увагу - деякі команди ламають (як, наприклад, вони порушують цикл) це. Наприклад, sshбез -nпрапора ефективно викличете вас уникнути циклу. Мабуть, для цього є вагома причина, але мені знадобилося певний час, щоб визначити, що спричинило збій мого коду, перш ніж я виявив це.
Олексій

6
як однолінійний: у той час як IFS = '' read -r рядок || [[-n "$ line"]]; робити відлуння "$ line"; зроблено <ім'я файлу
Джозеф Джонсон

8
@ OndraŽižka, це викликано ffmpegспоживанням stdin. Додайте </dev/nullдо ffmpegрядка, і він не зможе або використайте альтернативний FD для циклу. Такий підхід "альтернативного FD" виглядає так while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Чарльз Даффі

9
бурчить повторно: радить .shпродовження. Виконавчі файли в UNIX зазвичай взагалі не мають розширень (ви не запускаєтесь ls.elf), а наявність bash shebang (та лише інструментів для bash [[ ]]) та розширення, що передбачає сумісність POSIX sh, є внутрішньо суперечливою.
Чарльз Даффі

309

Я закликаю вас використовувати -rпрапор, на readякий стоїть:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Я цитую з man 1 read.

Інша справа - взяти ім'я файлу як аргумент.

Тут оновлений код:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
Обрізає провідний і кінцевий простір від лінії
barfuin

@Thomas і що відбувається з пробілами посередині? Підказка: небажана спроба виконання команди.
kmarsh

1
Це працювало для мене, на відміну від прийнятої відповіді.
нейромедіатор

3
@TranslucentCloud, якщо це спрацювало, і прийнята відповідь не стала, я підозрюю, що ваша оболонка була sh, ні bash; розширена тестова команда, що використовується в || [[ -n "$line" ]]синтаксисі у прийнятій відповіді, - це башизм. Це означає, що цей синтаксис насправді має відповідне значення: Це призводить до продовження циклу для останнього рядка у вхідному файлі, навіть якщо у нього немає нового рядка. Якщо ви хотіли це зробити сумісним з POSIX способом, ви хочете || [ -n "$line" ]скористатись, [а не [[.
Чарльз Даффі

3
З огляду на це, це все ще потрібно змінити, щоб встановити IFS=для readзапобігання обрізання пробілів.
Чарльз Даффі

132

Використання наведеного нижче шаблону Bash повинно дозволяти читати одне значення одночасно з файлу та обробляти його.

while read name; do
    # Do what you want to $name
done < filename

14
як однолінійний: при читанні імені; виконувати ехо $ {name}; зроблено <ім'я файлу
Джозеф Джонсон

4
@CalculusKnight, він "працював" лише тому, що ви не використовували достатньо цікавих даних для тестування. Спробуйте вміст із зворотним нахилом або має рядок, який містить лише *.
Чарльз Даффі

7
@ Матіас, припущення, які з часом виявляються помилковими, є одним з найбільших джерел помилок, як впливає на безпеку, так і в іншому випадку. Найбільша подія втрати даних, яку я коли-небудь бачив, була пов’язана зі сценарієм, який хтось припускав, що "буквально ніколи не з'явиться" - переповнення буфера, скидання випадкової пам'яті в буфер, який використовується для імені файлів, викликаючи сценарій, який створював припущення про те, які імена можуть коли-небудь існувати трапляються дуже і дуже невдало.
Чарльз Даффі

5
@Matthias, ... і це особливо вірно тут, оскільки зразки коду, показані в StackOverflow, призначені для використання в якості навчальних інструментів, щоб люди могли повторно використовувати зразки у власній роботі!
Чарльз Даффі

5
@Matthias, я абсолютно не погоджуюся з твердженням, що "вам слід розробити свій код лише для даних, які ви очікуєте". Несподівані випадки, коли ваші помилки, де ваші вразливості безпеки - обробка ними - це різниця між кодом slapdash і надійним кодом. Зрозуміло, що обробка не потребує фантазії - це може бути просто "вихід з помилкою" - але якщо у вас взагалі немає обробки, то ваша поведінка у несподіваних випадках не визначена.
Чарльз Даффі

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
Нічого проти інших відповідей, можливо, вони є більш складними, але я підтримую цю відповідь, тому що вона проста, читабельна і достатня для того, що мені потрібно. Зауважте, що для роботи текстовий файл, який слід прочитати, повинен закінчуватися порожнім рядком (тобто потрібно натиснутиEnter після останнього рядка), інакше останній рядок буде проігноровано. Принаймні, саме так сталося зі мною.
Антоніо Вініцій Менез Медей

12
Безкорисне використання кота, безумовно?
Брайан Агнеу

5
І цитування порушено; і не слід використовувати великі імена змінних, оскільки вони зарезервовані для використання в системі.
трійчастий

7
@AntonioViniciusMenezesMedei, ... більше того, я бачив, як люди несуть фінансові втрати, тому що вони припускали, що ці застереження для них ніколи не матимуть значення; не вдалося засвоїти належну практику; а потім дотримуватися звичок, до яких вони звикли, коли писали сценарії, які керували резервним копією критичних даних про виставлення рахунків. Навчитися робити речі правильно - це важливо.
Чарльз Даффі

6
Інша проблема тут полягає в тому, що труба відкриває нову нижню оболонку, тобто всі змінні, встановлені всередині циклу, не можуть бути прочитані після завершення циклу.
mxmlnkn

20

Багато людей розмістили надмірне оптимізацію рішення. Я не думаю, що це неправильно, але я смиренно вважаю, що менш оптимізованим рішенням буде бажано дозволити всім легко зрозуміти, як це працює. Ось моя пропозиція:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

Використання:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Якщо ви встановили IFSінше, ви отримаєте дивні результати.


34
Це жахливий метод . Будь ласка, не використовуйте його, якщо ви не хочете мати проблем з глобусом, які матимуть місце перед тим, як зрозуміти це!
gniourf_gniourf

13
@MUYBelgium Ви спробували з файлом, який містить сингл *у рядку? У всякому разі, це антипатерн . Не читайте рядки за .
gniourf_gniourf

2
@ OndraŽižka, readпідхід - це найкращий підхід за допомогою консенсусу громади . Застереження, яке ви згадуєте у своєму коментарі, - це те, що застосовується, коли ваш цикл виконує команди (такі як ffmpeg), які читаються зі stdin, тривіально вирішуються за допомогою не-stdin FD для циклу або перенаправлення вводу таких команд. На відміну від цього, обхід помилки з forглобалінгу у вашому підході -loop означає внесення (а потім потребувати зворотного зв'язку) зміни глобальних параметрів оболонки.
Чарльз Даффі

1
@ OndraŽižka, ... крім того, forпідхід петлі використовується тут , означає , що весь зміст має бути прочитано до того , як цикл може почати виконання на все, що робить його абсолютно непридатним для використання , якщо ви цикл над гігабайтами даних , навіть якщо у вас є інваліди глобус; while readциклу не потрібно ні більше , ніж дані в одному рядку в магазин за один раз, тобто він може почати виконання в той час як зміст генерації подпроцесса все ще працює (таким чином , бути придатним для цілей потокової передачі), а також має обмежений обсяг пам'яті.
Чарльз Даффі

1
Насправді, навіть whileпідходи, засновані на основі, мають * -характерні проблеми. Дивіться коментарі прийнятої відповіді вище. Не сперечаючись проти ітерації над тим, що файли є антипатерном.
Єгор Ганс

9

Якщо вам потрібно обробити і вхідний файл, і введення користувача (або що-небудь ще з stdin), тоді використовуйте наступне рішення:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

На основі прийнятої відповіді та на посібнику з перенаправлення баш-хакерів .

Тут ми відкриваємо дескриптор файлу 3 для файлу, переданого як аргумент сценарію, і пропонуємо readвикористовувати цей дескриптор як input ( -u 3). Таким чином, ми залишаємо дескриптор введення за замовчуванням (0), приєднаний до терміналу або іншого джерела входу, здатного читати введення користувача.


7

Для правильного поводження з помилками:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

Не могли б ви додати трохи пояснень?
Тайлер Крістіан

На жаль, він пропускає останній рядок у файлі.
ungalcrys

... а також, через відсутність котирування, лінійки, що містять символи символів - як описано в BashPitfalls №14 .
Чарльз Даффі

0

Далі буде роздруковано вміст файлу:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Ця відповідь насправді нічого не додає до існуючих відповідей, не працює через помилку помилки / помилки та багато в чому порушується.
Конрад Рудольф

0

Використовуйте інструмент IFS (внутрішній роздільник поля) в bash, визначає символ за допомогою розділення рядків на маркери, за замовчуванням включає < tab > / < space > / < newLine >

крок 1 : Завантажте файлові дані та вставіть у список:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

крок 2 : тепер повторіть і надрукуйте вихід:

for line in "${array[@]}"
  do
    echo "$line"
  done

ехо-специфічний індекс у масиві : доступ до змінної у масиві:

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