Use ler como um prompt dentro de um loop while dirigido por leitura?

6

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 não sei explorar -

  1. Para atribuição use outro filehandle em vez de stdin
  2. Use um loop for em vez de ... | while read ... ... Eu não sei como atribuir várias variáveis dentro de um for loop

    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
    
por Debanjan Basu 03.08.2018 / 10:55

3 respostas

7

Se eu acertei, acho que você deseja fazer um loop sobre as listas de valores e, em seguida, read another dentro do loop.

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

1. Emular matrizes com strings

Ter arrays 2D seria bom, mas não é realmente possível no Bash. Se seus valores não tiverem espaços em branco, uma solução alternativa para aproximar isso é inserir cada conjunto de três números em uma string e dividir as strings 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 pipe por outro redirecionamento para liberar stdin

Outra possibilidade é ter o read a b c lido 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 quiser 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. Tome a entrada do usuário de stderr em vez disso

Ou, ao contrário, usando um pipe como no seu exemplo, mas peça ao usuário que insira read de stderr (fd 2) em vez de stdin de onde o duto 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

A leitura de stderr é um pouco estranha, mas na verdade funciona frequentemente em uma sessão interativa. (Também é possível abrir explicitamente /dev/tty , supondo que você queira realmente ignorar qualquer redirecionamento, é isso que o material less usa para obter a entrada do usuário mesmo quando os dados são canalizados para ele.)

Embora usar stderr como esse pode não funcionar em todos os casos, e se você estiver usando algum comando externo em vez de read , precisará pelo menos adicionar vários redirecionamentos ao comando.

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

4. Cortar partes de uma matriz conforme necessário

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

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 , b etc se quiser nomes para as variáveis, mas Zsh faria isso muito mais bem .

    
por 03.08.2018 / 11:23
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 ksh93 shell:

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
    
por 03.08.2018 / 13:10
1

Há apenas um /dev/stdin , read lerá em qualquer lugar onde for usado (por padrão).

A solução é usar algum 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')"

Em 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 ) irá 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 algum outro 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

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

    
por 04.08.2018 / 06:42