Por que devo colocar o comando lido em um subshell enquanto uso o pipeline [duplicado]

Nov 30 2020

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|yredirecionará a saída de xpara a entrada de y. Por read a; read a b; echo $aque não consigo obter a entrada, mas uma subcamada pode?

Respostas

8 muru Dec 01 2020 at 03:10

O principal problema aqui é agrupar os comandos corretamente. Subshells são um problema secundário.

x|yirá redirecionar a saída de xpara a entrada dey

Sim, mas x | y; znão vai redirecionar a saída xpara ambos ye z.

Em df . | read a; read a b; echo "$a", o pipeline apenas se conecta df .e read aos outros comandos não têm conexão com esse pipeline. Você tem que agrupar os reads: 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 echocomando deve estar no mesmo subshell que o reads. 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.

3 glennjackman Dec 01 2020 at 03:10

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}' )
2 StephenHarris Dec 01 2020 at 03:11

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 acomando.

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 basho lado direito de um pipeline sendo executado em um subshell, então os $a $bvalores não são acessíveis fora do whileloop, mas esse é um problema diferente!