Багатовимірна для петель


12

Чи є спосіб вказати кілька змінних (а не лише цілі числа) в forциклі bash? У мене можуть бути 2 файли, що містять довільний текст, з яким мені потрібно буде працювати.

Що мені функціонально потрібно, це щось подібне:

for i in $(cat file1) and j in $(cat file2); do command $i $j; done

Будь-які ідеї?

Відповіді:


11

По-перше, не читайте рядки за допомогою , оскільки існує декілька неминучих проблем із читанням рядків за допомогою розділення слів.

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

while read -r x && read -r y <&3; do
    ...
done <file1 3<file2

З’єднати більш загальне рішення важко через те, коли readповертаються помилкові та кілька інших причин. У цьому прикладі можна прочитати довільну кількість потоків і повернутися після найкоротшого або найдовшого введення.

#!/usr/bin/env bash

# Open the given files and assign the resulting FDs to arrName.
# openFDs arrname file1 [file2 ...]
openFDs() {
    local x y i arr=$1

    [[ -v $arr ]] || return 1
    shift

    for x; do
        { exec {y}<"$x"; } 2>/dev/null || return 1
        printf -v "${arr}[i++]" %d "$y"
    done
}

# closeFDs FD1 [FD2 ...]
closeFDs() {
    local x
    for x; do
        exec {x}<&-
    done
}

# Read one line from each of the given FDs and assign the output to arrName.
# If the first argument is -l, returns false only when all FDs reach EOF.
# readN [ -l ] arrName FD1 [FD2 ...]
readN() {
    if [[ $1 == -l ]]; then
        local longest
        shift
    else
        local longest=
    fi

    local i x y status arr=$1
    [[ -v $arr ]] || return 1
    shift

    for x; do
        if IFS= read -ru "$x" "${arr}[i]" || { unset -v "${arr}[i]"; [[ ${longest+_} ]] && return 1; }; then
            status=0
        fi
        ((i++))
    done
    return ${status:-1}
}

# readLines file1 [file2 ...]
readLines() {
    local -a fds lines
    trap 'closeFDs "${fds[@]}"' RETURN
    openFDs fds "$@" || return 1

    while readN -l lines "${fds[@]}"; do
        printf '%-1s ' "${lines[@]}"
        echo
    done
}

{
    readLines /dev/fd/{3..6} || { echo 'error occured' >&2; exit 1; }
} <<<$'a\nb\nc\nd' 3<&0 <<<$'1\n2\n3\n4\n5' 4<&0 <<<$'x\ny\nz' 5<&0 <<<$'7\n8\n9\n10\n11\n12' 6<&0

# vim: set fenc=utf-8 ff=unix ts=4 sts=4 sw=4 ft=sh nowrap et:

Отже, залежно від того, readNотримає -l, вихід є або

a 1 x 7
b 2 y 8
c 3 z 9
d 4 10
5 11
12

або

a 1 x 7
b 2 y 8
c 3 z 9

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


||не працює для мене. Він обробляє file1 потім file2 , але це зберегти файли синхронізуються з &&, який виходить з той час як цикл по першому EOF . - GNU bash 4.1.5
Peter.O

У мене не було вільної хвилини для тестування - так, ви, мабуть, хотіли б обох елементів за ітерацію. Оператор ||"списку" коротке замикання, як у більшості мов. Безумовно, на це відповіли тисячу разів раніше ...
ormaaj

Справа не в частоті того, як часто щось згадувалося раніше. Швидше, це те, що код, який ви зараз показуєте у своїй відповіді , не працює . Виходячи з вашого " ймовірно, хочу обох елементів ..", це здається, що ви все ще не тестували його.
Пітер.О

@ Peter.O Я знаю. Це було виправлено.
ormaaj

2

Зроблено-зроблено; зроблено - вам потрібно дві форти та дві крапки:

for i in $(< file1); do for j in $(< file2); do echo $i $j;done ; done

File2, звичайно, обробляється для кожного слова у file1 один раз.

Використання <замість кота має бути непересічним підвищенням продуктивності у більшості випадків. Підпроцес не задіяний. (Невпевнений щодо останнього речення).

Без стиску:

for i in $(< file1)
do
  for j in $(< file2)
  do
    echo $i $j
  done
done

Якщо ви не любите читати слова, а рядки та зберігати пробіли, використовуйте час і читайте:

while read line; do while read second; do echo "${line}${second}"; done <file2 ; done <file1

Якщо ви хочете додати 2 файли, а не вкладати їх:

cat file1 file2 | while read line; do echo "${line}"; done

Якщо ви хочете скопіювати 2 файли, використовуйте пасту:

paste file1 file2

1
Ви кажете, що підпроцесор не викликається. Чи не () підзаголовок? Я не хочу бути кумедним, але я не впевнений, що зараз відбувається. Наприклад, я знаю, що, коли у мене є сценарій, і я не хочу, щоб він переводив поточну оболонку, наприклад, з CD, я це роблю всередині (). Також біг (сон 4) і, наприклад, з ПС показує, що процес все ще працює. Чи можете ви, будь ласка, докладно.
Silverrocker

Ну - вибачте, я дуже сильно дію без фундаментів. Я думав, що так зцілив, але, можливо, це лише <порівняно з cat. І я не впевнений, як це перевірити, і коли це стане семантично важливим. Якщо виклики труб, змінні в підпроцесах не перетворюються на основний процес, але у нас немає змінних. Я пробиваю критичне речення, поки хтось не зможе його прояснити.
користувач невідомий

0

whileмає синтаксис інтересу. Ви можете поставити кілька команд перед do ... whileциклом, і відповідний випадок, можливо, повинен буде перемкнути цю функцію, залежно від ваших конкретних вимог: чи читаєте ви до кінця найдовшого файлу, або лише до кінця найкоротшого.

Наприклад, read || readпросто не працює (відповідно до вимог запитання), бо, коли читається перший файл true, читання другого файлу пропускається, поки перший файл не буде прочитаний від початку до кінця ... Потім, оскільки статус все ще триває true, поки цикл продовжується і читає другий файл від початку до кінця.

read && readбуде читати файли одночасно (синхронно), якщо ви хочете прочитати лише найкоротший файл. Однак якщо ви хочете прочитати обидва файли до eof, тоді вам потрібно працювати з while'sвимогами синтаксису, тобто. по команді безпосередньо передdo while тим цикл виробництва ненульового коду повернення вирватися з циклу While.

Ось приклад того, як читати обидва файли до eof

while IFS= read -r line3 <&3 || ((eof3=1))
      IFS= read -r line4 <&4 || ((eof4=1))
      !((eof3 & eof4))
do
    echo "$line3, $line4"
done 3<file3 4<file4

(можливо, ви захочете перевірити eof3 та eof4 перед прочитаним, але загальна ідея є, особливо в остаточному істинному / хибному стані.

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