Multivariabel Untuk Loop

12

Apakah ada cara untuk menentukan beberapa variabel (bukan hanya bilangan bulat) dalam forloop bash? Saya mungkin memiliki 2 file yang mengandung teks sewenang-wenang yang harus saya kerjakan.

Apa yang saya butuhkan secara fungsional adalah sesuatu seperti ini:

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

Ada ide?

pengguna488244
sumber

Jawaban:

11

Pertama, Jangan membaca baris untuk , karena ada beberapa masalah yang tidak dapat dihindari dengan membaca baris melalui pemisahan kata.

Dengan asumsi file dengan panjang yang sama, atau jika Anda hanya ingin mengulang sampai dua file lebih pendek dibaca, solusi sederhana dimungkinkan.

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

Menyusun solusi yang lebih umum sulit karena ketika readmengembalikan salah dan beberapa alasan lainnya. Contoh ini dapat membaca jumlah aliran acak dan kembali setelah input terpendek atau terpanjang.

#!/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:

Jadi tergantung pada apakah readNmendapat -l, outputnya juga

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

atau

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

Harus membaca beberapa aliran dalam satu lingkaran tanpa menyimpan semuanya ke dalam beberapa array tidak terlalu umum. Jika Anda hanya ingin membaca array Anda harus melihatnya mapfile.

ormaaj
sumber
||tidak bekerja untuk saya. Ini memproses file1 kemudian file2 , tetapi itu tetap membuat file disinkronkan dengan &&, yang keluar dari loop sementara setelah bukti pertama . - GNU bash 4.1.5
Peter.O
Saya tidak punya waktu luang untuk menguji - ya Anda mungkin ingin kedua elemen per iterasi. Operator ||"daftar" mengalami hubungan arus pendek seperti di sebagian besar bahasa. Tentunya ini telah dijawab ribuan kali sebelumnya ...
ormaaj
Intinya bukan frekuensi seberapa sering sesuatu telah disebutkan sebelumnya. Sebaliknya, kode yang saat ini Anda tunjukkan dalam jawaban Anda tidak berfungsi . Berdasarkan "Anda mungkin menginginkan kedua elemen ..", sepertinya Anda masih belum mengujinya.
Peter.O
@ Peter. O Saya sadar. Sudah diperbaiki.
ormaaj
2

Dilakukan; selesai - Anda perlu dua fors dan dua done:

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

File2, tentu saja, diproses untuk setiap kata dalam file1 satu kali.

Menggunakan <bukannya cat harusnya merupakan peningkatan kinerja yang biasa-biasa saja dalam banyak kasus. Tidak ada subproses yang terlibat. (Tidak yakin tentang kalimat terakhir).

Terkompresi:

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

Jika Anda tidak suka membaca kata-kata, tetapi garis, dan pertahankan spasi putih, gunakan sementara dan baca:

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

Jika Anda ingin menambahkan 2 file, dan tidak membuat sarang:

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

Jika Anda ingin men-zip 2 file, gunakan tempel:

paste file1 file2
Pengguna tidak diketahui
sumber
1
Anda mengatakan tidak ada subproses yang terlibat. Bukankah () sebuah subkulit? Saya tidak ingin menjadi raksasa tetapi saya tidak yakin apa yang terjadi sekarang. Sebagai contoh, saya tahu bahwa ketika saya memiliki skrip dan saya tidak ingin itu mencemari shell saat ini dengan cd misalnya, saya melakukannya di dalam (). Juga berjalan (tidur 4) & misalnya diikuti oleh ps menunjukkan proses masih berjalan. Tolong jelaskan.
Silverrocker
Yah - maaf, saya sangat banyak bertindak tanpa dasar di sini. Saya pikir saya mendengarnya, tapi mungkin itu hanya <dibandingkan dengan cat. Dan saya tidak yakin bagaimana cara mengujinya, dan kapan itu akan menjadi sangat penting. Jika pipa dipanggil, variabel dalam subproses tidak muncul hingga proses utama, tetapi kami tidak memiliki variabel di sini. Saya menyerang melalui kalimat kritis, sampai seseorang dapat mengklarifikasi hal itu.
pengguna tidak diketahui
0

whilememiliki sintaksis interesitng. Anda dapat menempatkan beberapa perintah sebelum do ... whileloop, dan case yang dimaksud mungkin perlu menyulap fitur ini, tergantung pada persyaratan khusus Anda: apakah Anda membaca hingga akhir file terpanjang, atau hanya ke ujung yang terpendek.

Sebagai contoh, read || readhanya tidak bekerja (sesuai kebutuhan pertanyaan ini), becase ketika membaca dari file pertama adalah true, file kedua ini dibaca adalah dilewati sampai file pertama sudah dibaca dari awal sampai akhir ... Kemudian, karena status masih true, loop sementara berlanjut dan membaca file kedua dari awal hingga selesai.

read && readakan membaca file secara bersamaan (sinkron) jika Anda hanya ingin membaca sejauh file terpendek. Namun, jika Anda ingin membaca kedua file eof, maka Anda harus bekerja dengan while'spersyaratan sintaks, yaitu. dengan perintah segera sebelum para do whilelingkaran menghasilkan kode kembali non-nol untuk keluar dari sementara loop.

Berikut adalah contoh cara membaca kedua file ke 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

(Anda mungkin ingin menguji eof3 dan eof4 sebelum membaca, tetapi gagasan umum ada di sana, terutama dalam kondisi benar / salah akhir.

Peter.O
sumber