in bash, letto dopo che una pipe non sta impostando valori


22

Modifica: il titolo originale era "lettura fallita in bash"

Con ksh sto usando leggi come un modo conveniente per separare i valori:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 
2 1
$

Ma fallisce in bash:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 

$

Non ho trovato un motivo nella pagina man perché non riesce, qualche idea?


2
Questo è discusso (in qualche modo in modo oscuro) nella pagina 024 delle FAQ di Greg's Bash .
Scott,

Risposte:


27

bash esegue il lato destro di una pipeline in un contesto di subshell , quindi le modifiche alle variabili (che è ciò che readfa) non vengono conservate - muoiono quando lo fa la subshell, alla fine del comando.

Invece, puoi usare la sostituzione di processo :

$ read a b dump < <(echo 1 2 3 4 5)
$ echo $b $a
2 1

In questo caso, readviene eseguito all'interno della nostra shell primaria e il nostro comando di produzione dell'output viene eseguito nella subshell. La <(...)sintassi crea una subshell e collega il suo output a una pipe, che reindirizziamo all'input readcon l' <operazione ordinaria . Poiché readeseguito nella nostra shell principale, le variabili sono impostate correttamente.

Come sottolineato in un commento, se il tuo obiettivo è letteralmente dividere una stringa in variabili in qualche modo, puoi usare una stringa qui :

read a b dump <<<"1 2 3 4 5"

Presumo che ci sia molto di più, ma questa è un'opzione migliore se non lo è.


3
O addirittura read a b dump <<< '1 2 3 4 5'.
Choroba,

Grazie a tutti, tra l'altro ho notato che mksh (su cygwin) sta facendo lo stesso di bash.
Emmanuel,

@Michael Homer Buona spiegazione! Ho trovato un altro esempio che può spiegare che tutti i comandi in cantiere gestito in proprio subshell: cat /etc/passwd | (read -r line ; echo $line). Ma il prossimo echodei $linequali non è in fase di pipeline non ha messo nulla sullo schermo, perché il valore esisteva solo tra parentesi (subshell). Spero, aiuta qualcuno.
Yurij Goncharuk,

17

Questo non è un bashbug in quanto POSIXconsente entrambi bashe kshcomportamenti, portando alla sfortunata discrepanza che stai osservando.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12

Inoltre, ogni comando di una pipeline multi-comando si trova in un ambiente subshell; come estensione, tuttavia, qualsiasi o tutti i comandi in una pipeline possono essere eseguiti nell'ambiente corrente. Tutti gli altri comandi devono essere eseguiti nell'attuale ambiente shell.

Tuttavia, con bash 4.2e più recenti, è possibile impostare l' lastpipeopzione in script non interattivi per ottenere il risultato previsto, ad esempio:

#!/bin/bash

echo 1 2 3 4 5 | read a b dump
echo before: $b $a 
shopt -s lastpipe
echo 1 2 3 4 5 | read a b dump
echo after: $b $a 

Produzione:

before:
after: 2 1

1
+1 grazie per le informazioni "lastpipe". scusate il ritardo
Emmanuel,

1
il problema lastpipeè che non funziona in altre shell (es. trattino). non c'è praticamente alcun modo per fare questo breve portatile di correre tutto in quella subshell, vedere stackoverflow.com/questions/36268479/...
anarcat

1
@anarcat Questo è corretto ma la domanda posta qui riguardava bash.
jlliagre,

@anarcat: questo potrebbe cambiare in futuro poiché si desidera cambiare POSIX per un altro motivo (stato PIPE) vedere qui: unix.stackexchange.com/questions/476834/… Cambiare altre shell non è banale, ci sono voluti diversi mesi riscrivere il parser e l'interpeter sulla Bourne Shell (bosh) per implementare il comportamento più veloce del moderno ksh.
schily,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.