Перебирая содержимое файла в Bash

997

Как мне перебрать каждую строку текстового файла с помощью Bash?

С помощью этого скрипта:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

Я получаю этот вывод на экране:

Start!
./runPep.sh: line 3: syntax error near unexpected token '('
./runPep.sh: line 3: 'for p in (peptides.txt)'

(Позже я хочу сделать что-то более сложное с $p чем просто выводить на экран.)


Переменная окружения SHELL (из env):

SHELL=/bin/bash

Вывод /bin/bash --version:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

вывод cat/proc/version:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

Файл peptides.txt содержит:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
  • 7
    О, я вижу, что здесь произошло много вещей: все комментарии были удалены, и вопрос был вновь открыт. Просто для справки, принятый ответ в строке «Чтение файла», присваивающий значение переменной, решает проблему каноническим способом и должен быть предпочтительнее, чем принятый здесь.
Теги:
loops
io

11 ответов

1579
Лучший ответ

Один из способов сделать это:

while read p; do
  echo "$p"
done <peptides.txt

Как указано в комментариях, это имеет побочные эффекты: обрезание начальных пробелов, интерпретация последовательностей обратной косой черты и пропуск задней строки, если в ней отсутствует завершающий перевод строки. Если это проблемы, вы можете сделать:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

В исключительных случаях, если тело цикла может читать из стандартного ввода, вы можете открыть файл, используя другой файловый дескриптор:

while read -u 10 p; do
  ...
done 10<peptides.txt

Здесь 10 - просто произвольное число (отличное от 0, 1, 2).

  • 7
    Как мне интерпретировать последнюю строку? Файл peptides.txt перенаправляется на стандартный ввод и как-то на весь блок while?
  • 10
    Msgstr "Вставьте peptides.txt в этот цикл while, чтобы команде read было что потреблять". Мой метод "кошка" похож, посылая вывод команды в блок while для потребления "read", только он запускает другую программу для выполнения работы.
Показать ещё 16 комментариев
324
cat peptides.txt | while read line
do
   # do something with $line here
done
  • 57
    В общем, если вы используете «cat» только с одним аргументом, вы делаете что-то не так (или неоптимально).
  • 0
    Я попробовал это, и это работает (так же как и Бруно де Фрейн).
Показать ещё 17 комментариев
128

Вариант 1а: цикл "цикл": по одной строке: перенаправление ввода

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

Вариант 1b: цикл "цикл": одна строка за раз:
Откройте файл, прочитайте из файлового дескриптора (в данном случае файловый дескриптор # 4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

Вариант 2. Для цикла: чтение файла в одну переменную и анализ.
Этот синтаксис будет анализировать "строки" на основе любого пробела между токенами. Это все еще работает, потому что указанные строки входного файла являются однословными токенами. Если бы в строке было больше одного токена, этот метод не работал бы. Кроме того, чтение полного файла в одну переменную не является хорошей стратегией для больших файлов.

#!/bin/bash
filename='peptides.txt'
filelines='cat $filename'
echo Start
for line in $filelines ; do
    echo $line
done
  • 0
    Для варианта 1b: нужно ли снова закрывать дескриптор файла? Например, петля может быть внутренней петлей.
  • 3
    Дескриптор файла будет очищен при выходе из процесса. Явное закрытие может быть сделано для повторного использования числа fd. Чтобы закрыть fd, используйте другой exec с синтаксисом &, например: exec 4 <& -
Показать ещё 3 комментария
64

Это не лучше, чем другие ответы, но это еще один способ получить работу в файле без пробелов (см. комментарии). Я нахожу, что мне часто нужны однострочники для прокрутки списков в текстовых файлах без дополнительного шага использования отдельных файлов script.

for word in $(cat peptides.txt); do echo $word; done

Этот формат позволяет мне помещать все это в одну командную строку. Измените часть "echo $word" на то, что вы хотите, и вы можете выпустить несколько команд, разделенных точками с запятой. Следующий пример использует содержимое файла как аргументы в двух других сценариях, которые вы, возможно, написали.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

Или, если вы намереваетесь использовать это как редактор потока (learn sed), вы можете вывести вывод в другой файл следующим образом.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

Я использовал их как написано выше, потому что я использовал текстовые файлы, где я создал их по одному слову в строке. (См. Комментарии) Если у вас есть пробелы, которые вы не хотите разделить ваши слова/строки, он становится немного уродливым, но та же команда все еще работает следующим образом:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

Это просто говорит оболочке разделить только на новые строки, а не пробелы, а затем возвращает среду обратно к тому, что было ранее. На этом этапе вы можете захотеть поместить все это в оболочку script, а не сжать все это в одну строку.

Удачи!

  • 0
    Это не соответствует требованию (итерация по каждой строке), если файл содержит пробелы или символы табуляции, но может быть полезно, если вы хотите выполнить итерацию по каждому полю в файле, разделенном табуляцией / пробелом.
  • 3
    Bash $ (<peptides.txt), возможно, более элегантен, но все же неправильно, как правильно сказал Жоао, вы выполняете логику подстановки команд, где пробел или символ новой строки - это то же самое. Если в строке есть пробел, цикл выполняется ДВАЖДЫ или более для этой одной строки. Поэтому ваш код должен правильно читать: для слова в $ (<peptides.txt); делай .... Если ты точно знаешь, что пробелов нет, то строка равна слову, и ты в порядке.
Показать ещё 8 комментариев
44

Используйте цикл while, например:

while IFS= read -r line; do
   echo "$line"
done <file

Примечания:

  • 2
    Почему опция -r ?
  • 2
    @ DavidC.Rankin Опция -r предотвращает обратную косую черту. Note #2 является ссылкой, где это описано подробно ...
Показать ещё 5 комментариев
42

Еще несколько вещей, не охваченных другими ответами:

Чтение из файла с разделителями

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of 'read', it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, 'field3' will get all the values, including the third field plus the delimiter(s)
done < input.txt

Чтение из вывода другой команды, используя подстановку процесса

while read -r line; do
  # process the line
done < <(command ...)

Этот подход лучше, чем command... | while read -r line; do... command... | while read -r line; do... command... | while read -r line; do... потому что цикл while выполняется в текущей оболочке, а не в подоболочке, как в случае последней. См. Соответствующий пост . Переменная, измененная внутри цикла while, не запоминается.

Чтение с ввода с нулевым разделением, например, find... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

Связанные чтения: BashFAQ/020 - Как я могу найти и безопасно обрабатывать имена файлов, содержащие переводы строк, пробелы или оба?

Чтение из более чем одного файла одновременно

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the '&&'
done 3< input1.txt 4< input2.txt

Основываясь на ответе @chepner здесь:

-u является расширением bash. Для совместимости с POSIX каждый вызов будет выглядеть примерно так: read -r X <&3.

Чтение всего файла в массив (версии Bash ранее до 4)

while read -r line; do
    my_array+=("$line")
done < my_file

Если файл заканчивается неполной строкой (в конце отсутствует новая строка), то:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

Чтение всего файла в массив (версии Bash 4x и выше)

readarray -t my_array < my_file

или же

mapfile -t my_array < my_file

А потом

for line in "${my_array[@]}"; do
  # process the lines
done

Похожие сообщения:

  • 0
    обратите внимание, что вместо command < input_filename.txt вы всегда можете выполнить input_generating_command | command или command < <(input_generating_command)
13

Если вы не хотите, чтобы ваше чтение было нарушено символом новой строки, используйте -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

Затем запустите script с именем файла в качестве параметра.

11

Предположим, у вас есть этот файл:

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

Существует четыре элемента, которые изменят значение вывода файла, читаемого многими решениями Bash:

  1. Пустая строка 4;
  2. Начальные или конечные пробелы в двух строках;
  3. Сохранение значения отдельных строк (т.е. Каждая строка является записью);
  4. Строка 6 не заканчивается символом CR.

Если вы хотите, чтобы текстовый файл построчно включал в себя пустые строки и завершающие строки без CR, вы должны использовать цикл while и у вас должен быть альтернативный тест для последней строки.

Вот методы, которые могут изменить файл (по сравнению с тем, что возвращает cat):

1) Потерять последнюю строку, а также начальные и конечные пробелы:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

(Если вы делаете, while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt, вы сохраняете пробелы в while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt и в конце, но все равно теряете последний линия, если она не заканчивается CR)

2) Использование процесса подстановки с помощью cat приведет к считыванию всего файла одним глотком и потере значения отдельных строк:

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

(Если вы удалите " из $(cat/tmp/test.txt) вы прочитаете файл слово за словом, а не одним глотком. Также, вероятно, не то, что предназначено...)


Самый надежный и простой способ прочитать файл построчно и сохранить все пробелы:

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

Если вы хотите удалить ведущие и торговые пробелы, удалите часть IFS=:

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

(Текстовый файл без завершающего \n, в то время как довольно часто, считается ломались под POSIX. Если вы можете рассчитывать на ведомой \n Вам не нужно || [[ -n $line ]] в while цикл.)

Больше на BASH FAQ

  • 0
    Мой я спрашиваю, почему понизить?
4
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done
  • 6
    Этот ответ нуждается в предостережениях, упомянутых в ответе mightypile , и он может потерпеть неудачу, если любая строка содержит метасимволы оболочки (из-за без кавычек "$ x").
  • 6
    Я на самом деле удивлен, что люди еще не придумали обычные Не читать строки для ...
3

Вот мой пример реальной жизни, как петля строк другого выхода программы, проверка подстрок, сброс двойных кавычек из переменной, использование этой переменной за пределами цикла. Я думаю, что многие задают эти вопросы рано или поздно.

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

Объявить переменную за пределами цикла, установить значение и использовать его вне цикла, необходимо сделать < < < Синтаксис "$ (...)". Приложение необходимо запускать в контексте текущей консоли. Котировки вокруг команды сохраняют новые строки выходного потока.

Соответствие Loop для подстрок затем считывает пару name = value, разделяет правую часть last = character, отбрасывает первую цитату, катит последнюю цитату, мы имеем чистое значение, которое будет использоваться в другом месте.

  • 2
    Хотя ответ верен, я понимаю, как все закончилось здесь. Основной метод такой же, как предложили многие другие ответы. Плюс, он полностью тонет в вашем примере FPS.
1

@Peter: это может сработать для вас -

echo "Start!";for p in $(cat ./pep); do
echo $p
done

Это вернет результат -

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
  • 7
    Это очень плохо! Почему вы не читаете строки с «для» .
  • 2
    Этот ответ побеждает все принципы, изложенные в хороших ответах выше!
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню