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

27

Я хотел бы знать, есть ли какой-либо способ чтения из двух входных файлов во вложенном цикле 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. Я попытался использовать продолжение, разрыв, выход, но ни один из них не предназначен для достижения результата, который я ищу. Я хотел бы, чтобы скрипт считывал только одну строку из файла 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?

Джайпал Сингх
источник
Отличное решение от @codaddict здесь: stackoverflow.com/a/4011824/4095830 ->paste -d '\n' file1 file2
whoan

Ответы:

32

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

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

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') и process substitution ( <(...)), чтобы избежать цикла 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
lesmana
источник
17

Откройте два файла в разных файловых дескрипторах . Перенаправьте ввод 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» - есть ли способ получить результат с нулевым различием в этом случае? если вы хотите продолжить обработку до конца обоих файлов.

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

Жиль "ТАК - перестань быть злым"
источник
Спасибо @Gilles за дополнительные ссылки на дескриптор файла.
Джайпал Сингх
@ Жиль, возможно, я неправильно понял вас, но я не мог сделать цикл обработки самого длинного файла целиком (в моем случае это всегда $ fileA), поэтому я сделал это в отдельном вопросе: есть ли способ написать цикл так? что diff не замечает никакой разницы между входом и выходом? unix.stackexchange.com/questions/26780/… самым близким, что я мог получить, был diff, только обнаружив одну разницу.
ixtmixilix
3

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

Луцкая
источник
Спасибо @lutzky. pasteтоже круто
Джайпал Сингх
2

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

paste -d '\n' inp1.txt inp2.txt > outfile.txt
Shree
источник
0

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

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

Никхил Мулли
источник