Use read como um prompt dentro de um loop while conduzido por read?

9

Eu tenho um caso de uso em que preciso ler várias variáveis ​​no início de cada iteração e ler uma entrada do usuário no loop.

Possíveis caminhos para a solução que eu não sei explorar -

  1. Para atribuição, use outro manipulador de arquivos em vez de stdin
  2. Use um forloop em vez de ... | while read ...... Eu não sei como atribuir várias variáveis ​​dentro de um forloop

    echo -e "1 2 3\n4 5 6" |\
    while read a b c; 
    do 
      echo "$a -> $b -> $c";
      echo "Enter a number:";
      read d ;
      echo "This number is $d" ; 
    done
Debanjan Basu
fonte

Respostas:

9

Se eu entendi direito, acho que você deseja basicamente fazer um loop sobre listas de valores e depois readoutro dentro do loop.

Aqui estão algumas opções: 1 e 2 são provavelmente as mais saudáveis.

1. Emule matrizes com strings

Ter matrizes 2D seria bom, mas não é realmente possível no Bash. Se seus valores não tiverem espaço em branco, uma solução alternativa para aproximar isso é colar cada conjunto de três números em uma sequência e dividir as seqüências dentro do loop:

for x in "1 2 3" "4 5 6"; do 
  read a b c <<< "$x"; 
  read -p "Enter a number: " d
  echo "$a - $b - $c - $d ";
done

Claro que você também pode usar outro separador, por exemplo, for x in 1:2:3 ...e IFS=: read a b c <<< "$x".


2. Substitua o tubo por outro redirecionamento para liberar stdin

Outra possibilidade é ter a read a b cleitura de outro fd e direcionar a entrada para isso (isso deve funcionar em um shell padrão):

while read a b c <&3; do
    printf "Enter a number: "
    read d
    echo "$a - $b - $c - $d ";
done 3<<EOF
1 2 3
4 5 6
EOF

E aqui você também pode usar uma substituição de processo se desejar obter os dados de um comando: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(a substituição do processo é um recurso bash / ksh / zsh)


3. Aceite a entrada do usuário do stderr

Ou, ao contrário, utilizando um tubo como no seu exemplo, mas têm a entrada do usuário readde stderr(FD 2) em vez de stdinonde o tubo vem:

echo $'1 2 3\n4 5 6' |
while read a b c; do 
    read -u 2 -p "Enter a number: " d
    echo "$a - $b - $c - $d ";
done

Ler stderré um pouco estranho, mas na verdade geralmente funciona em uma sessão interativa. (Você também pode abrir explicitamente /dev/tty, supondo que você realmente queira ignorar qualquer redirecionamento, é isso que as coisas lessusam para obter a entrada do usuário, mesmo quando os dados são canalizados para ele.)

Embora o uso stderrdesse tipo possa não funcionar em todos os casos, e se você estiver usando algum comando externo em vez de read, pelo menos, precisará adicionar vários redirecionamentos ao comando.

Além disso, consulte Por que minha variável local é em um loop 'while read', mas não em outro loop aparentemente semelhante? para algumas questões relacionadas ... | while.


4. Corte partes de uma matriz, conforme necessário

Suponho que você também possa aproximar uma matriz 2D-ish copiando fatias de uma matriz unidimensional regular:

data=(1 2 3 
      4 5 6)

n=3
for ((i=0; i < "${#data[@]}"; i += n)); do
    a=( "${data[@]:i:n}" )
    read -p "Enter a number: " d
    echo "${a[0]} - ${a[1]} - ${a[2]} - $d "
done

Você também pode atribuir ${a[0]}etc. a a, betc , se quiser nomes para as variáveis, mas o Zsh faria isso muito mais bem .

ilkkachu
fonte
1
isso foi ótimo! E que ótima visão geral das diferentes soluções alternativas!
Debanjan Basu
@ StéphaneChazelas, ah sim, você está certo. Usar stderrassim é um pouco nojento, mas tive a lembrança de que algum utilitário faz isso. Mas tudo o que posso encontrar agora são aqueles que apenas usamos /dev/tty. Ah bem.
Ilkkachu
Observe que usar <&2(e também </dev/tty) evitar a leitura do stdin do script. Isso não vai funcionar printf '682\n739' | ./script. Observe também que read -psó funciona no bash.
Isaac
@Isaac, no que é lido pelo stderr, também há o whilecanal do eco para esse loop, então você realmente não pode usar o stdin do script de qualquer maneira ... read -utambém é bash, mas pode ser substituído por redirecionamentos, e <<<em o primeiro também não é padrão, mas é um pouco mais difícil de contornar.
Ilkkachu
Tudo poderia ser resolvido, por favor: leia minha resposta
Isaac
3

Existe apenas um /dev/stdin, readirá ler a partir dele em qualquer lugar em que for usado (por padrão).

A solução é usar outro descritor de arquivo em vez de 1 ( /dev/stdin).

Do código equivalente (no bash) ao que você postou [1] (veja abaixo),
basta adicionar 0</dev/tty(por exemplo) para ler o tty "real":

while read a b c
do    read -p "Enter a number: " d  0</dev/tty   # 0<&2 is also valid
      echo "$a -> $b -> $c and ++> $d"
done  <<<"$(echo -e '1 2 3\n4 5 6')"

Na execução:

$ ./script
Enter a number: 789
1 -> 2 -> 3 and ++> 789
Enter a number: 333
4 -> 5 -> 6 and ++> 333

Outra alternativa é usar 0<&2(o que pode parecer estranho, mas é válido).

Observe que a leitura de /dev/tty(também 0<&2) ignorará o stdin do script, isso não lerá os valores do eco:

$ echo -e "33\n44" | ./script

Outras soluções

O que é necessário é redirecionar uma entrada para outra fd (descritor de arquivo).
Válido em ksh, bash e zsh:

while read -u 7 a b c
do    printf "Enter a number: "
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<<"$(echo -e '1 2 3\n4 5 6')"

Ou, com exec:

exec 7<<<"$(echo -e '1 2 3\n4 5 6')"

while read -u 7 a b c
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Uma solução que funciona em sh ( <<<não funciona):

exec 7<<-\_EOT_
1 2 3
4 5 6
_EOT_

while read a b c  <&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Mas isso é provavelmente mais fácil de entender:

while read a b c  0<&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<-\_EOT_
1 2 3
4 5 6
_EOT_

1 código mais simples

Seu código é:

echo -e "1 2 3\n4 5 6" |\
while read a b c; 
do 
  echo "$a -> $b -> $c";
  echo "Enter a number: ";
  read d ;
  echo "This number is $d" ; 
done

Um código simplificado (no bash) é:

while read a b c
do    #0</dev/tty
      read -p "Enter a number: " d ;
      echo "$a -> $b -> $c and ++> $d";
done  <<<"$(echo -e '1 2 3\n4 5 6')"

Que, se executado, imprime:

$ ./script
1 -> 2 -> 3 and ++> 4 5 6

O que está apenas mostrando que o var d está sendo lido do mesmo /dev/stdin.

Isaac
fonte
2

Com zsh, você pode escrevê-lo:

for a b c (
  1 2 3
  4 5 6
  'more complex' $'\n\n' '*** values ***'
) {
  read 'd?Enter a number: '
  do-something-with $a $b $c $d
}

Para matrizes 2D, veja também o ksh93shell:

a=(
  (1 2 3)
  (4 5 6)
  ('more complex' $'\n\n' '*** values ***')
)
for i in "${!a[@]}"; do
  read 'd?Enter a number: '
  do-something-with "${a[i][0]}" "${a[i][1]}" "${a[i][2]}" "$d"
done
Stéphane Chazelas
fonte
nice ... Eu estava procurando por uma resposta baseada no bash, mas é bom saber!
Debanjan Basu