Як читати з двох вхідних файлів за допомогою циклу while


27

Мені хотілося дізнатись, чи є спосіб читання з двох вхідних файлів у вкладеному, а циклі по одному рядку. Наприклад, скажімо, що у мене є два файли 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') та підміна процесу ( <(...)), щоб уникнути циклу 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

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

Дивіться також Коли ви використовуєте додатковий дескриптор файлу? для отримання додаткової інформації про дескриптори файлів і чому так часто використовується "while IFS = read" замість `IFS =; поки читати..`? для пояснення IFS= read -r.


Дякуємо @Gilles за додаткові посилання на дескриптор файлів.
jaypal singh

@Gilles, можливо, я зрозумів тебе неправильно, але я не зміг зробити циклічний процес найдовшим файлом цілком (який у моєму випадку завжди $ fileA), тому я зробив це в окреме запитання, будучи: чи є спосіб написати цикл так що відмінність не помічає різниці між входом і виходом? unix.stackexchange.com/questions/26780/… найближче, що я міг отримати, було розрізняти лише один рядок різниці.
ixtmixilix

3

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


Дякую @lutzky pasteкруто теж.
jaypal singh

2

Спробуйте вказану нижче команду:

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

0

Як варіант, я припускаю, що ви можете прив'язувати файл до змінної масиву, прив'язуючи кожен рядок файлу до масиву [line_of_file_index], використовуючи команду bash mapfile. Однак я не впевнений, чи це лише Bash3 вище або Bash4.

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

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