Por que devo colocar o comando lido em um subshell enquanto uso o pipeline [duplicado]
O comando df .
pode nos mostrar em qual dispositivo estamos. Por exemplo,
me@ubuntu1804:~$ df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb1 61664044 8510340 49991644 15% /home
Agora eu quero pegar a corda /dev/sdb1
.
Tentei assim, mas não funcionou df . | read a; read a b; echo "$a"
:, este comando me deu uma saída vazia. Mas df . | (read a; read a b; echo "$a")
funcionará conforme o esperado.
Estou meio confuso agora.
Eu sei que (read a; read a b; echo "$a")
é uma subcamada, mas não sei por que tenho que fazer uma subcamada aqui. Pelo meu entendimento, x|y
redirecionará a saída de x
para a entrada de y
. Por read a; read a b; echo $a
que não consigo obter a entrada, mas uma subcamada pode?
Respostas
O principal problema aqui é agrupar os comandos corretamente. Subshells são um problema secundário.
x|y
irá redirecionar a saída dex
para a entrada dey
Sim, mas x | y; z
não vai redirecionar a saída x
para ambos y
e z
.
Em df . | read a; read a b; echo "$a"
, o pipeline apenas se conecta df .
e read a
os outros comandos não têm conexão com esse pipeline. Você tem que agrupar os read
s: df . | { read a; read a b; }
ou df . | (read a; read a b)
para o pipeline ser conectado a ambos.
No entanto, agora vem o problema do subshell: os comandos em um pipeline são executados em um subshell, portanto, definir uma variável neles não afeta o shell pai. Portanto, o echo
comando deve estar no mesmo subshell que o read
s. Assim: df . | { read a; read a b; echo "$a"; }
.
Agora, se você usa ( ... )
ou { ...; }
não faz nenhuma diferença particular aqui, já que os comandos em um pipeline são executados em subshells de qualquer maneira.
Uma alternativa é usar uma substição de processo :
{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"
A <(...)
substituição do processo executa o script contido (em um subshell), mas atua como um nome de arquivo, então você precisa primeiro <
redirecionar o conteúdo (que é a saída do script) para o script entre chaves. Os comandos agrupados são executados no shel ;.
Pode ser complicado deixar isso legível, mas você pode colocar qualquer espaço em branco arbitrário entre as chaves e a substituição do processo.
{
read header
read filesystem rest
} < <(
df .
)
echo "$filesystem"
E pode ser mais fácil usar uma ferramenta externa para extrair o sistema de arquivos:
filesystem=$( df . | awk 'NR == 2 {print $1}' )
Seu primeiro comando
df . | read a; read a b; echo "$a"
efetivamente é interpretado como
( df . | read a ) ; read a b; echo "$a"
Portanto, o pipeline alimenta apenas o read a
comando.
Como você deseja várias leituras do pipeline, precisa agrupar os comandos.
Agora, não precisa ser uma subcamada; pode ser um agrupamento ..
bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs
Mais comumente, você pode querer um loop
bash-4.2$ df | while read a > do > read a b > echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb
Há um problema secundário com bash
o lado direito de um pipeline sendo executado em um subshell, então os $a
$b
valores não são acessíveis fora do while
loop, mas esse é um problema diferente!