For Loops Multivariáveis

11

Existe uma maneira de especificar múltiplas variáveis (não apenas inteiros) em for loops em bash ? Eu posso ter 2 arquivos contendo texto arbitrário com os quais eu precisaria trabalhar.

O que eu preciso funcionalmente é algo assim:

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

Alguma idéia?

    
por user488244 20.06.2012 / 22:52

3 respostas

11

Primeiro, Não leia as linhas com , pois há vários problemas inevitáveis com a leitura de linhas por meio da divisão de palavras.

Assumindo arquivos de tamanho igual, ou se você quiser apenas fazer um loop até que o menor de dois arquivos seja lido, uma solução simples é possível.

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

Colocar uma solução mais geral é difícil porque read retorna falso 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:

Então, dependendo se readN recebe -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ários arrays não é tão comum. Se você quiser apenas ler arrays, deve dar uma olhada em mapfile .

    
por 20.06.2012 / 23:05
2

Fazer-do; done - você precisa de dois fors e dois dones:

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

O File2 é, obviamente, processado para cada palavra no arquivo1 uma vez.

Usando < em vez de gato deve ser um ganho de desempenho normal na maioria dos casos. Nenhum subprocesso envolvido. (inseguro 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 acrescentar 2 arquivos e não aninhá-los:

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

Se você deseja zipar 2 arquivos, use o comando colar:

paste file1 file2
    
por 20.06.2012 / 23:48
0

while tem uma sintaxe interessante. Você pode colocar vários comandos antes do loop do ... while , e o caso em questão pode ter que manipular esse recurso, dependendo das suas necessidades específicas de: você lê até o final do arquivo mais longo ou apenas até o final do arquivo mais curto.

Por exemplo, read || read simplesmente não funciona (de acordo com os requisitos da pergunta), porque uma leitura do primeiro arquivo é true , a segunda leitura do arquivo é ignorada até que o primeiro arquivo tenha sido lido do início ao fim ... Então, como o status ainda é true , o loop while continua e lê o segundo arquivo do início ao fim.

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

Aqui está um exemplo de como ler os dois arquivos para 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 querer testar eof3 e eof4 antes da leitura, mas a idéia geral está lá, especialmente na condição final verdadeira / falsa.

    
por 21.06.2012 / 15:49