¿Por qué debo poner el comando read en un subshell mientras uso la canalización [duplicar]

Nov 30 2020

El comando df .puede mostrarnos en qué dispositivo estamos. Por ejemplo,

me@ubuntu1804:~$ df .
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sdb1       61664044 8510340  49991644  15% /home

Ahora quiero conseguir la cuerda /dev/sdb1.

Intenté así pero no funcionó: df . | read a; read a b; echo "$a"este comando me dio una salida vacía. Pero df . | (read a; read a b; echo "$a")funcionará como se esperaba.

Estoy un poco confundido ahora.

Sé que (read a; read a b; echo "$a")es una subcapa, pero no sé por qué tengo que hacer una subcapa aquí. Según tengo entendido, x|yredirigirá la salida de xa la entrada de y. ¿Por qué read a; read a b; echo $ano puede obtener la entrada pero una subcapa sí?

Respuestas

8 muru Dec 01 2020 at 03:10

El principal problema aquí es agrupar los comandos correctamente. Las subcapas son un problema secundario.

x|yredirigirá la salida de xa la entrada dey

Sí, pero x | y; zno redirigirá la salida de xambos yy z.

En df . | read a; read a b; echo "$a", la canalización solo se conecta df .y read alos otros comandos no tienen conexión con esa canalización. Tienes que agrupar los reads juntos: df . | { read a; read a b; }o df . | (read a; read a b)que la tubería se conecte a ambos.

Sin embargo, ahora viene el problema de la subcapa: los comandos en una canalización se ejecutan en una subcapa, por lo que establecer una variable en ellos no afecta al shell principal. Por lo tanto, el echocomando debe estar en la misma subcapa que reads. Por lo tanto: df . | { read a; read a b; echo "$a"; }.

Ahora bien, si usa ( ... )o { ...; }no hace una diferencia particular aquí, ya que los comandos en una canalización se ejecutan en subcapas de todos modos.

3 glennjackman Dec 01 2020 at 03:10

Una alternativa es utilizar una sustitución de proceso :

{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"

La <(...)sustitución del proceso ejecuta el script contenido (en una subcapa), pero actúa como un nombre de archivo, por lo que necesita el primero <para redirigir el contenido (que es la salida del script) al script reforzado. Los comandos agrupados se ejecutan en el shel actual ;.

Puede ser complicado conseguir que esto sea legible, pero puede poner cualquier espacio en blanco arbitrario entre las llaves y la sustitución del proceso.

{
    read header
    read filesystem rest
} < <(
    df .
)
echo "$filesystem"

Y podría ser más fácil usar una herramienta externa para extraer el sistema de archivos:

filesystem=$( df . | awk 'NR == 2 {print $1}' )
2 StephenHarris Dec 01 2020 at 03:11

Tu primer comando

df . | read a; read a b; echo "$a"

efectivamente se interpreta como

( df . | read a ) ; read a b; echo "$a"

Entonces, la canalización solo se alimenta del read acomando.

Dado que desea varias lecturas de la canalización, debe agrupar los comandos.

Ahora no tiene por qué ser una subcapa; podría ser una agrupación ...

bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs

Más comúnmente, es posible que desee un bucle

bash-4.2$ df | while read a > do > read a b > echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb

Hay un problema secundario con bashel lado derecho de una canalización que se ejecuta en una subcapa, por lo que los $a $bvalores no son accesibles fuera del whileciclo, ¡pero ese es un problema diferente!