Как читать из двух входных файлов, используя цикл while

Я хотел знать, есть ли способ чтения из двух входных файлов во вложенном цикле по одной строке за раз. Например, скажем, у меня есть два файла FileA и FileB .

FiLea:

 [jaypal:~/Temp] cat filea this is File A line1 this is File A line2 this is File A line3 

FILEB:

 [jaypal:~/Temp] cat fileb this is File B line1 this is File B line2 this is File B line3 

Текущий пример скрипта:

 [jaypal:~/Temp] cat read.sh #!/bin/bash while read lineA do echo $lineA while read lineB do echo $lineB done < fileb done < filea 

Исполнение:

 [jaypal:~/Temp] ./read.sh this is File A line1 this is File B line1 this is File B line2 this is File B line3 this is File A line2 this is File B line1 this is File B line2 this is File B line3 this is File A line3 this is File B line1 this is File B line2 this is File B line3 

Проблема и желаемый результат:

Это полностью перекрывает FileB для каждой строки в FileA. Я пытался использовать continue, break, exit, но ни один из них не предназначен для достижения результата, который я ищу. Я хотел бы, чтобы сценарий читал только одну строку из файла A, а затем одну строку из FileB и выходил из цикла и продолжал вторую строку файла A и вторую строку файла B. Что-то похожее на следующий скрипт –

 [jaypal:~/Temp] cat read1.sh #!/bin/bash count=1 while read lineA do echo $lineA lineB=`sed -n "$count"p fileb` echo $lineB count=`expr $count + 1` done < filea [jaypal:~/Temp] ./read1.sh this is File A line1 this is File B line1 this is File A line2 this is File B line2 this is File A line3 this is File B line3 

Можно ли это сделать с помощью цикла while?

Если вы точно знаете, что какой-то символ никогда не встречается в первом файле, вы можете использовать пасту.

Пример вставки с использованием разделителя по умолчанию:

 paste file1 file2 | while IFS="$(printf '\t')" read -r f1 f2 do printf 'f1: %s\n' "$f1" printf 'f2: %s\n' "$f2" done 

Пример использования пасты с помощью @ :

 paste -d@ file1 file2 | while IFS="@" read -r f1 f2 do printf 'f1: %s\n' "$f1" printf 'f2: %s\n' "$f2" done 

Обратите внимание, что этого достаточно, если в первом файле символ не встречается. Это происходит потому, что read будет игнорировать IFS при заполнении последней переменной. Поэтому, даже если @ во втором файле, он не будет разделен.

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

 while IFS=$'\t' read -r f1 f2 do printf 'f1: %s\n' "$f1" printf 'f2: %s\n' "$f2" done < <(paste file1 file2) 

Используемые функции Bash: ansi c string ( $'\t' ) и подстановка процесса ( <(...) ), чтобы избежать цикла while в задаче подоболочки .

Если вы не можете быть уверены, что какой-либо символ никогда не встречается в обоих файлах, вы можете использовать дескрипторы файлов .

 while true do read -r f1 <&3 || break read -r f2 <&4 || break printf 'f1: %s\n' "$f1" printf 'f2: %s\n' "$f2" done 3<file1 4<file2 

Не много испытано. Могу пробить пустые строки.

Файловые дескрипторы числа 0, 1 и 2 уже используются для stdin, stdout и stderr, соответственно. Файловые дескрипторы от 3 и выше (как правило) бесплатны. Руководство bash предупреждает об использовании дескрипторов файлов больше 9, потому что они «используются внутренне».

Обратите внимание, что дескрипторы открытых файлов наследуются к функциям оболочки и внешним программам. Функции и программы, наследующие открытый файловый дескриптор, могут читать (и записывать) файловый дескриптор. Вы должны позаботиться о закрытии всех дескрипторов файлов, которые не требуются перед вызовом функции или внешней программы.

Вот та же самая программа, что и выше, с фактической работой (печатью), отделенной от мета-работы (чтение строки за строкой из двух файлов параллельно).

 work() { printf 'f1: %s\n' "$1" printf 'f2: %s\n' "$2" } while true do read -r f1 <&3 || break read -r f2 <&4 || break work "$f1" "$f2" done 3<file1 4<file2 

Теперь мы делаем вид, что у нас нет контроля над кодом работы, и этот код по какой-либо причине пытается прочитать из дескриптора файла 3.

 unknowncode() { printf 'f1: %s\n' "$1" printf 'f2: %s\n' "$2" read -r yoink <&3 && printf 'yoink: %s\n' "$yoink" } while true do read -r f1 <&3 || break read -r f2 <&4 || break unknowncode "$f1" "$f2" done 3<file1 4<file2 

Вот пример вывода. Обратите внимание, что вторая строка из первого файла «украдена» из цикла.

 f1: file1 line1 f2: file2 line1 yoink: file1 line2 f1: file1 line3 f2: file2 line2 

Вот как вы должны закрыть дескрипторы файла перед вызовом внешнего кода (или любого кода, если на то пошло).

 while true do read -r f1 <&3 || break read -r f2 <&4 || break # this will close fd3 and fd4 before executing anycode anycode "$f1" "$f2" 3<&- 4<&- # note that fd3 and fd4 are still open in the loop done 3<file1 4<file2 

Откройте два файла в разных файловых дескрипторах . Перенаправить вход read встроенного в дескриптор, к которому подключен файл, к которому вы хотите подключиться. В bash / ksh / zsh вы можете написать read -u 3 вместо read <&3 .

 while IFS= read -r lineA && IFS= read -r lineB <&3; do echo "$lineA"; echo "$lineB" done <fileA 3<fileB 

Этот фрагмент останавливается, когда обрабатывается самый короткий файл. См. Чтение двух файлов в цикле IFS while. Есть ли способ получить нулевой результат diff в этом случае? если вы хотите продолжить обработку до конца обоих файлов.

См. Также Когда вы использовали бы дополнительный дескриптор файла? для получения дополнительной информации о дескрипторах файлов и почему `while IFS = read` используется так часто, вместо` IFS =; в то время как читать..`? для объяснения IFS= read -r .

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

В качестве альтернативы, я полагаю, вы могли бы разбить файл на переменную массива, привязывая каждую строку файла к массиву [line_of_file_index], используя команду mapfile bash. Однако я не уверен, что это только для Bash3 выше или Bash4.

http://wiki.bash-hackers.org/commands/builtin/mapfile

Попробуйте выполнить команду ниже:

 paste -d '\n' inp1.txt inp2.txt > outfile.txt