Multivariável para loops

12

Existe uma maneira de especificar várias variáveis ​​(não apenas números inteiros) em forloops bash? Eu posso ter 2 arquivos contendo texto arbitrário com os quais eu precisaria trabalhar.

O que eu funcionalmente preciso é algo como isto:

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

Alguma ideia?

user488244
fonte

Respostas:

11

Primeiro, não leia linhas com para , pois existem vários problemas inevitáveis ​​com a leitura de linhas por meio da divisão de palavras.

Supondo que arquivos de tamanho igual ou se você deseja fazer um loop apenas até que o menor dos dois arquivos sejam lidos, é possível uma solução simples.

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

Montar uma solução mais geral é difícil, porque quando readretorna retornos falsos e várias outras razões. Este exemplo pode ler um número arbitrário de fluxos e retornar após a entrada mais curta ou mais longa.

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

Portanto, dependendo de se readNobtém -l, a saída é

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

ou

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

Ter que ler vários fluxos em um loop sem salvar tudo em várias matrizes não é tão comum. Se você quiser apenas ler matrizes, deve dar uma olhada mapfile.

ormaaj
fonte
||não funciona para mim. Ele processar file1 então file2 , mas ele faz manter os arquivos sincronizados com &&, que sai do enquanto ciclo após a primeira EOF . - GNU bash 4.1.5
Peter.O
Não tive um momento livre para testar - sim, você provavelmente desejaria os dois elementos por iteração. O ||operador "list" está em curto-circuito, como na maioria dos idiomas. Certamente, isso já foi respondido mil vezes antes ... #
1113 ormaaj
A questão não é a frequência com que algo foi mencionado antes. Em vez disso, é que o código que você está mostrando atualmente em sua resposta não funciona . Com base no seu " provavelmente quer os dois elementos ..", parece que você ainda não o testou.
precisa saber é o seguinte
@ Peter.O Estou ciente. Foi consertado.
ormaaj
2

Feito; pronto - você precisa de dois fors e dois dones:

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

O arquivo2 é, é claro, processado para cada palavra no arquivo1 uma vez.

Usar <em vez de gato deve ser um ganho de desempenho notável na maioria dos casos. Nenhum subprocesso envolvido. (Incerto sobre a última frase).

Descomprimido:

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

Se você não gosta de ler palavras, mas linhas, e preservar o espaço em branco, use while e leia:

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

Se você deseja anexar 2 arquivos e não aninhá-los:

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

Se você deseja compactar 2 arquivos, use colar:

paste file1 file2
Usuário desconhecido
fonte
1
Você diz que não há subprocesso envolvido. O () não é um subshell? Não quero ser pendente, mas não tenho certeza do que está acontecendo agora. Por exemplo, eu sei que quando tenho um script e não quero poluir o shell atual com os CDs, por exemplo, faço-o dentro de um (). Também em execução (suspensão 4) e, por exemplo, seguido por um ps, mostra-me que o processo ainda está em execução. Você pode por favor elaborar.
precisa
Bem - desculpe, estou agindo muito sem fundamento aqui. Eu pensei que tinha ouvido isso, mas talvez seja apenas o <comparado a cat. E não tenho certeza de como testá-lo e quando ele se tornará semanticamente importante. Se os pipes forem invocados, as variáveis ​​nos subprocessos não chegarão ao processo principal, mas não temos variáveis ​​aqui. Repito a frase crítica, até que alguém possa esclarecer.
usuário desconhecido
0

whilepossui uma sintaxe de interação. Você pode colocar vários comandos antes do do ... whileloop, e o caso em questão pode precisar manipular esse recurso, dependendo dos requisitos específicos de: você lê o final do arquivo mais longo ou apenas o final do mais curto?

Por exemplo, read || readsimplesmente não funciona (conforme os requisitos da pergunta), pois quando uma leitura do primeiro arquivo é true, a leitura do segundo arquivo é ignorada até que o primeiro arquivo seja lido do início ao fim ... Então, porque o status ainda é true, o loop while continua e lê o segundo arquivo do início ao fim.

read && readlerá os arquivos simultaneamente (de forma síncrona) se você quiser apenas ler o arquivo mais curto. No entanto, se você quiser ler os dois arquivos eof, precisará trabalhar com os while'srequisitos de sintaxe, por exemplo. pelo comando imediatamente antes do do whileloop, produzindo um código de retorno diferente de zero para interromper o loop while.

Aqui está um exemplo de como ler os dois arquivos no 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

(você pode testar eof3 e eof4 antes da leitura, mas a ideia geral está lá, especialmente na condição final verdadeiro / falso.

Peter.O
fonte