Comprender la sustitución de comandos de leer un archivo de Bash

11

Estoy tratando de entender cómo exactamente Bash trata la siguiente línea:

$(< "$FILE")

Según la página de manual de Bash, esto es equivalente a:

$(cat "$FILE")

y puedo seguir la línea de razonamiento para esta segunda línea. Bash realiza una expansión variable en $FILE, ingresa la sustitución del comando, pasa el valor de $FILEa cat, cat emite el contenido de $FILEla salida estándar, la sustitución del comando finaliza reemplazando toda la línea con la salida estándar resultante del comando interno, y Bash intenta ejecutarlo como Un simple comando.

Sin embargo, para la primera línea que mencioné anteriormente, lo entiendo como: Bash realiza la sustitución de variables en $FILE, Bash se abre $FILEpara leer en la entrada estándar, de alguna manera la entrada estándar se copia en la salida estándar , finaliza la sustitución de comandos y Bash intenta ejecutar el estándar resultante salida.

¿Puede alguien explicarme cómo va el contenido de $FILEstdin a stdout?

Stanley Yu
fuente

Respuestas:

-3

El <no es directamente un aspecto de la sustitución del comando bash . Es un operador de redireccionamiento (como una tubería), que algunos shells permiten sin un comando (POSIX no especifica este comportamiento).

Quizás sería más claro con más espacios:

echo $( < $FILE )

esto es efectivamente * igual que el más seguro para POSIX

echo $( cat $FILE )

... que también es eficaz *

echo $( cat < $FILE )

Comencemos con esa última versión. Esto se ejecuta catsin argumentos, lo que significa que leerá de la entrada estándar. $FILEse redirige a la entrada estándar debido a <, por catlo que sus contenidos se colocan en la salida estándar. La $(command)sustitución luego empuja catla salida de en argumentos para echo.

En bash(pero no en el estándar POSIX), puede usar <sin un comando. bash(y zshy kshpero no dash) interpretará eso como si cat <, aunque sin invocar un nuevo subproceso. Como esto es nativo del shell, es más rápido que ejecutar literalmente el comando externo cat. * Por eso digo "efectivamente lo mismo que".

Adam Katz
fuente
Entonces, en el último párrafo, cuando dice " bashinterpretará eso como cat filename", ¿quiere decir que este comportamiento es específico para la sustitución de comandos? Porque si corro < filenamesolo, bash no lo atrapa. No generará nada y me devolverá a un mensaje.
Stanley Yu
Todavía se necesita un comando. @cuonglm alteró mi texto original cat < filenameal cat filenameque me opongo y puedo volver.
Adam Katz
1
Una tubería es un tipo de archivo. El operador de shell |crea una tubería entre dos subprocesos (o, con algunos shells, desde un subproceso hasta la entrada estándar del shell). El operador de shell $(…)crea una tubería desde un subproceso al propio shell (no a su entrada estándar). El operador de shell <no involucra una tubería, solo abre un archivo y mueve el descriptor de archivo a la entrada estándar.
Gilles 'SO- deja de ser malvado'
3
< fileno es lo mismo que cat < file(excepto en zshdonde es como $READNULLCMD < file). < filees perfectamente POSIX y solo se abre filepara leer y luego no hace nada (por lo que fileestá cerca de inmediato). Es $(< file)o `< file`ese es un operador especial de ksh, zshy bash(y el comportamiento se deja sin especificar en POSIX). Vea mi respuesta para más detalles.
Stéphane Chazelas
2
Para poner el comentario de @ StéphaneChazelas en otra luz: para una primera aproximación, $(cmd1) $(cmd2)generalmente será lo mismo que $(cmd1; cmd2). Pero mira el caso donde cmd2está < file. Si decimos $(cmd1; < file), el archivo no se lee, pero $(cmd1) $(< file)sí. Por lo tanto, es incorrecto decir que $(< file)es solo un caso ordinario $(command)con un comando de < file.   $(< …)es un caso especial de sustitución de comando, y no un uso normal de redirección.
Scott, el
13

$(<file)(también funciona con `<file`) es un operador especial del shell Korn copiado por zshy bash. Se parece mucho a la sustitución de comandos, pero en realidad no lo es.

En shells POSIX, un comando simple es:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

Todas las partes son opcionales, puede tener solo redirecciones, solo comandos, solo asignaciones o combinaciones.

Si hay redirecciones pero no hay comando, las redirecciones se realizan (por > filelo tanto, se abriría y truncaría file), pero luego no sucede nada. Entonces

< file

Se abre filepara leer, pero luego no sucede nada ya que no hay comando. Entonces el filese cierra y eso es todo. Si $(< file)fuera una simple sustitución de comando , entonces se expandiría a nada.

En la especificación POSIX , en $(script), si scriptconsiste solo en redirecciones, eso produce resultados no especificados . Eso es para permitir ese comportamiento especial del shell Korn.

En ksh (aquí probado con ksh93u+), si el script consta de uno y solo un comando simple (aunque se permiten comentarios antes y después) que consiste solo en redirecciones (sin comando, sin asignación) y si la primera redirección es un stdin (fd 0) solo entrada ( <, <<o <<<) redirección, entonces:

  • $(< file)
  • $(0< file)
  • $(<&3)(también en $(0>&3)realidad, ya que es el mismo operador)
  • $(< file > foo 2> $(whatever))

pero no:

  • $(> foo < file)
  • ni $(0<> file)
  • ni $(< file; sleep 1)
  • ni $(< file; < file2)

luego

  • todos menos la primera redirección se ignoran (se analizan)
  • y se expande al contenido del archivo / heredoc / herestring (o lo que pueda leerse del descriptor de archivo si se usan cosas como <&3) menos los caracteres de línea nueva.

como si estuviera usando $(cat < file)excepto que

  • la lectura se realiza internamente por el shell y no por cat
  • no hay tubería ni proceso adicional involucrado
  • Como consecuencia de lo anterior, dado que el código interno no se ejecuta en una subshell, cualquier modificación permanece a partir de entonces (como en $(<${file=foo.txt})o $(<file$((++n))))
  • los errores de lectura (aunque no errores al abrir archivos o duplicar descriptores de archivos) se ignoran silenciosamente.

En zsh, es el mismo, salvo que ese comportamiento especial sólo se activa cuando sólo hay una redirección de entrada de archivo ( <fileo 0< file, sin <&3, <<<here, < a < b...)

Sin embargo, excepto al emular otras conchas, en:

< file
<&3
<<< here...

es decir, cuando solo hay redirecciones de entrada sin comandos, fuera de la sustitución de comandos, se zshejecuta $READNULLCMD(un buscapersonas por defecto), y cuando hay redirecciones de entrada y salida, $NULLCMD( catpor defecto), incluso si $(<&3)no se reconoce como ese especial operador, todavía funcionará como kshinvocando a un localizador para que lo haga (ese localizador actúa como si catsu stdout fuera una tubería).

Sin embargo, mientras que ksh's $(< a < b)se expandirían al contenido de a, en zsh, se expande con el contenido de ay b(o simplemente bsi la multiosopción está desactivada), $(< a > b)sería copiar aa by ampliar a nada, etc.

bash tiene un operador similar pero con algunas diferencias:

  • Se permiten comentarios antes pero no después:

    echo "$(
       # getting the content of file
       < file)"
    

    funciona pero:

    echo "$(< file
       # getting the content of file
    )"
    

    se expande a la nada.

  • como en zsh, solo una redirección de stdin de archivo, aunque no hay retroceso a a $READNULLCMD, por lo tanto $(<&3), $(< a < b)realice las redirecciones pero expanda a nada.

  • por alguna razón, aunque bashno se invoca cat, aún se bifurca un proceso que alimenta el contenido del archivo a través de una tubería, lo que lo convierte en una optimización mucho menor que en otros shells. En efecto, es como un lugar $(cat < file)donde catsería un edificio cat.
  • Como consecuencia de lo anterior, cualquier cambio realizado dentro se pierde después (en el $(<${file=foo.txt})mencionado anteriormente, por ejemplo, esa $fileasignación se pierde después).

En bash, IFS= read -rd '' var < file (también funciona zsh) es una forma más efectiva de leer el contenido de un archivo de texto en una variable. También tiene la ventaja de preservar los caracteres de la nueva línea final. Consulte también $mapfile[file]en zsh(en el zsh/mapfilemódulo y solo para archivos normales) que también funciona con archivos binarios.

Tenga en cuenta que las variantes basadas en pdksh kshtienen algunas variaciones en comparación con ksh93. De interés, en mksh(uno de esos shells derivados de pdksh), en

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

está optimizado porque el contenido del documento here (sin los caracteres finales) se expande sin utilizar un archivo o canalización temporal, como es el caso de los documentos here, lo que lo convierte en una sintaxis efectiva de citas de varias líneas.

Ser portátil para todas las versiones de ksh, zshy bash, lo mejor es limitarse a $(<file)evitar solo comentarios y tener en cuenta que las modificaciones a las variables realizadas dentro pueden o no conservarse.

Stéphane Chazelas
fuente
¿Es correcto que $(<)sea ​​un operador en los nombres de archivo? ¿Está <en $(<)un operador de redireccionamiento, o no es un operador por sí solo, y debe ser parte de todo el operador $(<)?
Tim
@Tim, no importa mucho cómo quieras llamarlos. $(<file)está destinado a expandir el contenido de fileuna manera similar a como lo $(cat < file)haría. La forma en que se realiza varía de un shell a otro, que se describe en detalle en la respuesta. Si lo desea, puede decir que es un operador especial que se activa cuando lo que parece una sustitución de comando (sintácticamente) contiene lo que parece una única redirección de stdin (sintácticamente), pero nuevamente con advertencias y variaciones dependiendo del shell como se enumera aquí .
Stéphane Chazelas
@ StéphaneChazelas: Fascinante, como siempre; He marcado esto como favorito. Entonces, ¿ n<&my n>&mhacer lo mismo? No lo sabía, pero supongo que no es demasiado sorprendente.
Scott
@ Scott, sí, ambos hacen un dup(m, n). Puedo ver alguna evidencia de ksh86 usando stdio y algo fdopen(fd, "r" or "w"), por lo que podría haber importado entonces. Pero usar stdio en un shell no tiene mucho sentido, así que no espero que encuentres ningún shell moderno en el que eso haga la diferencia. Una diferencia es que >&nes dup(n, 1)(abreviatura de 1>&n), mientras que <&nes dup(n, 0)(abreviatura de 0<&n).
Stéphane Chazelas
Derecha. Excepto, por supuesto, que se llama la forma de dos argumentos de la llamada de duplicación del descriptor de archivo dup2(); dup()toma solo un argumento y, como open(), usa el descriptor de archivo más bajo disponible. (Hoy aprendí que hay una dup3()función )
Scott
8

Porque lo bashhace internamente para usted, expande el nombre del archivo y agrega el archivo a la salida estándar, como si fuera a hacerlo $(cat < filename). Es una función bash, tal vez necesites mirar el bashcódigo fuente para saber exactamente cómo funciona.

Aquí la función para manejar esta característica (desde bashcódigo fuente, archivo builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

Una nota que $(<filename)no es exactamente equivalente a $(cat filename); este último fallará si el nombre del archivo comienza con un guión -.

$(<filename)era originalmente de ksh, y fue agregado a bashde Bash-2.02.

Cuonglm
fuente
1
cat filenamefallará si el nombre del archivo comienza con un guión porque cat acepta las opciones. Puede solucionar eso en la mayoría de los sistemas modernos con cat -- filename.
Adam Katz
-1

Piense en la sustitución de comandos como ejecutar un comando como de costumbre y volcar la salida en el punto donde ejecuta el comando.

La salida de los comandos se puede usar como argumentos para otro comando, para establecer una variable e incluso para generar la lista de argumentos en un bucle for.

foo=$(echo "bar")establecerá el valor de la variable $fooen bar; La salida del comando echo bar.

Sustitución de comando

iyrin
fuente
1
Creo que de la pregunta queda bastante claro que el OP comprende los conceptos básicos de la sustitución de comandos; la pregunta es sobre el caso especial de $(< file), y él no necesita un tutorial sobre el caso general. Si está diciendo que $(< file)es solo un caso ordinario $(command)con un comando de < file, entonces está diciendo lo mismo que Adam Katz dice , y ambos están equivocados.
Scott, el