Цикл через содержимое файла в 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 is (от 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
файл пептидов.txt содержит:
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
11 ответов
один из способов сделать это:
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).
вариант 1a: while loop: одна строка за раз: перенаправление ввода
#!/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: for loop: чтение файла в одну переменную и синтаксический анализ.
Этот синтаксис будет анализировать "линии" на основе любого пробела между токенами. Это все еще работает, потому что данное входные строки файла-это однословные маркеры. Если бы было более одного токена на строку, то этот метод не работал бы. Кроме того, чтение полного файла в одну переменную не является хорошей стратегией для больших файлов.
#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
echo $line
done
это не лучше, чем другие ответы, но это еще один способ выполнить работу в файле без пробелов (см. комментарии). Я обнаружил, что мне часто нужны однострочные, чтобы копаться в списках в текстовых файлах без дополнительного шага использования отдельных файлов сценариев.
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
Это просто говорит оболочке разбиваться только на новые строки, а не пробелы, а затем возвращает среду обратно к тому, что было ранее. На этом этапе вы можете рассмотреть вопрос о том, чтобы поместить все это в сценарий оболочки, а не сжимать все это в одну строку.
удачи!
используйте цикл while, например:
while IFS= read -r line; do
echo "$line"
done <file
Примечания:
Если вы не установите
IFS
правильно, вы потеряете отступ.
еще несколько вещей, не охваченных другими ответами:
чтение из файла с разделителями
# ':' 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 ...
потому что цикл 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
по теме:
Если вы не хотите, чтобы ваше чтение было нарушено символом новой строки, используйте -
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
done < ""
затем запустите скрипт с именем файла в качестве параметра.
Предположим, у вас есть этот файл:
$ 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:
- пустая строка 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
вместо этого вы сохраняете начальные и конечные пробелы, но все равно теряете последнюю строку, если она не заканчивается 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
#!/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
вот мой пример реальной жизни, как зацикливать строки другого вывода программы, проверять подстроки, отбрасывать двойные кавычки из переменной, использовать эту переменную вне цикла. Я думаю, что довольно многие задают эти вопросы рано или поздно.
##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 match для подстрок затем читает name=value пара, разбивает правую часть последнего = характер, падает первая цитата, падает последняя цитата, Мы имеем чистое значение, который нужно использовать в другом месте.
@Peter: это может сработать для вас -
echo "Start!";for p in $(cat ./pep); do
echo $p
done
это будет выходной-
Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL