Comprensione della sostituzione del comando Read-a-File di Bash


11

Sto cercando di capire come Bash tratta esattamente la seguente riga:

$(< "$FILE")

Secondo la pagina man di Bash, ciò equivale a:

$(cat "$FILE")

e posso seguire la linea di ragionamento per questa seconda linea. Bash esegue l'espansione variabile attivata $FILE, inserisce la sostituzione del comando, passa il valore di $FILEa cat, cat restituisce il contenuto $FILEall'output standard, la sostituzione del comando termina sostituendo l'intera riga con l'output standard risultante dal comando all'interno e Bash tenta di eseguirlo come un semplice comando.

Tuttavia, per la prima riga che ho menzionato sopra, lo capisco come: Bash esegue la sostituzione variabile su $FILE, Bash si apre $FILEper la lettura sull'input standard, in qualche modo l'input standard viene copiato sull'output standard , la sostituzione del comando termina e Bash tenta di eseguire lo standard risultante produzione.

Qualcuno può spiegarmi come $FILEva il contenuto di stdin a stdout?

Risposte:


-3

Non <è direttamente un aspetto della sostituzione del comando bash . È un operatore di reindirizzamento (come una pipe), che alcune shell consentono senza un comando (POSIX non specifica questo comportamento).

Forse sarebbe più chiaro con più spazi:

echo $( < $FILE )

questo è effettivamente * lo stesso del più sicuro POSIX

echo $( cat $FILE )

... che è anche efficacemente *

echo $( cat < $FILE )

Cominciamo con quest'ultima versione. Questo funziona catsenza argomenti, il che significa che leggerà dallo standard input. $FILEviene reindirizzato nello standard input a causa di <, quindi catmette il suo contenuto nello standard output. La $(command)sottosezione inserisce quindi catl'output in argomenti per echo.

In bash(ma non nello standard POSIX), è possibile utilizzare <senza un comando. bash(e zshe kshma non dash) interpreterà che, come se cat <, pur senza invocare un nuovo sottoprocesso. Poiché è nativo della shell, è più veloce dell'esecuzione letterale del comando esterno cat. * Questo è il motivo per cui dico "effettivamente lo stesso di".


Quindi nell'ultimo paragrafo quando dici " bashlo interpreterai come cat filename", intendi questo comportamento specifico per la sostituzione dei comandi? Perché se corro < filenameda solo, bash non lo catapulta. Non emetterà nulla e tornerà a un prompt.
Stanley Yu,

È ancora necessario un comando. @cuonglm alterato il mio testo originale da cat < filenamea cat filenamecui mi oppongo e può tornare.
Adam Katz,

1
Una pipe è un tipo di file. L'operatore shell |crea una pipe tra due sottoprocessi (o, con alcune shell, da un sottoprocesso all'input standard della shell). L'operatore shell $(…)crea una pipe da un sottoprocesso alla shell stessa (non al suo input standard). L'operatore shell <non coinvolge una pipe, apre solo un file e sposta il descrittore di file nell'input standard.
Gilles 'SO- smetti di essere malvagio' il

3
< filenon è lo stesso di cat < file(tranne in zshdove è come $READNULLCMD < file). < fileè perfettamente POSIX e si apre solo fileper la lettura e quindi non fa nulla (quindi fileè immediatamente vicino). È $(< file)o `< file`che è un operatore speciale di ksh, zshe bash(e il comportamento non è specificato in POSIX). Vedi la mia risposta per i dettagli.
Stéphane Chazelas,

2
Per mettere il commento di @ StéphaneChazelas sotto un'altra luce: ad una prima approssimazione, $(cmd1) $(cmd2)sarà in genere lo stesso di $(cmd1; cmd2). Ma guarda il caso in cui si cmd2trova < file. Se diciamo $(cmd1; < file), il file non viene letto, ma $(cmd1) $(< file)lo è. Quindi è errato dire che $(< file)è solo un caso normale $(command)con un comando di < file.   $(< …)è un caso speciale di sostituzione dei comandi e non un normale utilizzo del reindirizzamento.
Scott,

14

$(<file)(funziona anche con `<file`) è un operatore speciale della shell Korn copiata da zshe bash. Assomiglia molto alla sostituzione dei comandi, ma in realtà non lo è.

Nelle shell POSIX, un semplice comando è:

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

Tutte le parti sono opzionali, puoi avere solo reindirizzamenti, solo comandi, solo assegnazioni o combinazioni.

Se ci sono reindirizzamenti ma nessun comando, i reindirizzamenti vengono eseguiti (quindi > fileverrebbe aperto e troncato file), ma poi non accade nulla. Così

< file

Si apre fileper la lettura, ma poi non succede nulla in quanto non vi è alcun comando. Quindi fileviene chiuso e basta. Se $(< file)fosse una semplice sostituzione di comando , non si espanderebbe a nulla.

Nella specifica POSIX , in $(script), se è scriptcostituito solo da reindirizzamenti, produce risultati non specificati . Questo per consentire quel comportamento speciale della shell Korn.

In ksh (qui testato con ksh93u+), se lo script è costituito da un solo e semplice comando (sebbene i commenti siano consentiti prima e dopo) che consiste solo di reindirizzamenti (nessun comando, nessuna assegnazione) e se il primo reindirizzamento è uno stdin (fd 0) inserisci solo ( <, <<o <<<) reindirizzamento, quindi:

  • $(< file)
  • $(0< file)
  • $(<&3)(anche in $(0>&3)realtà poiché è in effetti lo stesso operatore)
  • $(< file > foo 2> $(whatever))

ma no:

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

poi

  • tutti tranne il primo reindirizzamento vengono ignorati (vengono analizzati)
  • e si espande nel contenuto del file / heredoc / herestring (o qualsiasi cosa possa essere letta dal descrittore di file se si usano cose del genere <&3) meno i caratteri di nuova riga finali.

come se usando $(cat < file)tranne quello

  • la lettura viene eseguita internamente dalla shell e non da cat
  • nessuna pipa né processo extra è coinvolto
  • come conseguenza di quanto sopra, dato che il codice interno non viene eseguito in una subshell, qualsiasi modifica rimane in seguito (come in $(<${file=foo.txt})o $(<file$((++n))))
  • errori di lettura (sebbene non errori durante l'apertura di file o la duplicazione di descrittori di file) vengono silenziosamente ignorati.

In zsh, è lo stesso, tranne che quel comportamento speciale viene attivato solo quando c'è solo una redirezione di input del file ( <fileo 0< file, no <&3, <<<here, < a < b...)

Tuttavia, tranne quando si emulano altre shell, in

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

cioè quando ci sono solo reindirizzamenti di input senza comandi, al di fuori della sostituzione dei comandi, zshviene eseguito $READNULLCMD(un pager per impostazione predefinita) e quando ci sono sia reindirizzamenti di input e output, il $NULLCMD( catdi default), quindi anche se $(<&3)non viene riconosciuto come speciale operatore, funzionerà comunque come kshse invocando un cercapersone per farlo (quel cercapersone si comporta come catpoiché il suo stdout sarà una pipe).

Tuttavia, mentre ksh's $(< a < b)sarebbe espandersi per il contenuto di a, a zsh, si espande per il contenuto di ae b(o solo bse l' multiosopzione è disabilitata), $(< a > b)si copia aa bed espandere a nulla, etc.

bash ha un operatore simile ma con alcune differenze:

  • i commenti sono consentiti prima ma non dopo:

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

    funziona ma:

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

    non si espande nel nulla.

  • come in zsh, solo un reindirizzamento stdin del file, anche se non è possibile ricorrere a un $READNULLCMD, quindi $(<&3), $(< a < b)esegui i reindirizzamenti ma espandi a nulla.

  • per qualche motivo, sebbene bashnon invochi cat, bifora comunque un processo che alimenta il contenuto del file attraverso una pipe rendendolo molto meno ottimizzato rispetto ad altre shell. In effetti è come un punto in $(cat < file)cui catsarebbe incorporato cat.
  • come conseguenza di quanto sopra, qualsiasi modifica apportata all'interno viene persa in seguito ( $(<${file=foo.txt})ad esempio, sopra citato, tale $fileincarico viene perso in seguito).

In bash, IFS= read -rd '' var < file (funziona anche in zsh) è un modo più efficace per leggere il contenuto di un file di testo in una variabile. Ha anche il vantaggio di preservare i nuovi caratteri finali. Vedi anche $mapfile[file]in zsh(nel zsh/mapfilemodulo e solo per file normali) che funziona anche con file binari.

Si noti che le varianti basate su pdksh kshhanno alcune varianti rispetto a ksh93. Di interesse, in mksh(una di quelle conchiglie derivate da pdksh), in

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

è ottimizzato in quanto il contenuto del documento Here (senza i caratteri finali) viene espanso senza l'utilizzo di un file o di una pipe temporanea, come altrimenti accade per i documenti qui, il che lo rende un'efficace sintassi di quotazione su più righe.

Essere portabili su tutte le versioni di ksh, zshe bash, il migliore è limitarsi a $(<file)evitare solo commenti e tenendo presente che le modifiche alle variabili apportate all'interno possono o meno essere preservate.


È corretto che $(<)sia un operatore sui nomi dei file? Si trova <in $(<)un operatore di reindirizzamento o non è un operatore da solo e deve far parte dell'intero operatore $(<)?
Tim

@Tim, non importa molto come li vuoi chiamare. $(<file)è destinato ad espandersi al contenuto di filein un modo simile a come $(cat < file)farebbe. Il modo in cui viene fatto varia da shell a shell, come descritto dettagliatamente nella risposta. Se vuoi, puoi dire che è un operatore speciale che viene attivato quando ciò che assomiglia a una sostituzione di comando (sintatticamente) contiene ciò che sembra un singolo reindirizzamento stdin (sintatticamente), ma di nuovo con avvertenze e variazioni a seconda della shell come elencato qui .
Stéphane Chazelas,

@ StéphaneChazelas: affascinante, come al solito; Ho aggiunto questo ai segnalibri. Quindi, n<&me n>&mfai la stessa cosa? Non lo sapevo, ma immagino non sia troppo sorprendente.
Scott,

@Scott, sì, entrambi fanno a dup(m, n). Riesco a vedere alcune prove di ksh86 usando stdio e alcune fdopen(fd, "r" or "w"), quindi potrebbe avere importanza allora. Ma usare lo stdio in una shell ha poco senso, quindi non mi aspetto che troverai una shell moderna dove ciò farà la differenza. Una differenza è che >&nè dup(n, 1)(abbreviazione di 1>&n), mentre <&nè dup(n, 0)(abbreviazione di 0<&n).
Stéphane Chazelas,

Giusto. Tranne, ovviamente, viene chiamata la forma a due argomenti della chiamata di duplicazione del descrittore di file dup2(); dup()accetta solo un argomento e, come open(), utilizza il descrittore di file disponibile più basso. (Oggi ho imparato che esiste una dup3()funzione .)
Scott,

8

Perché lo bashfa internamente per te, espandi il nome del file e gatta il file sullo standard output, come se dovessi farlo $(cat < filename). È una funzione bash, forse è necessario esaminare il bashcodice sorgente per sapere esattamente come funziona.

Ecco la funzione per gestire questa funzione (da bashcodice sorgente, file 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 che $(<filename)non è esattamente equivalente a $(cat filename); quest'ultimo fallirà se il nome del file inizia con un trattino -.

$(<filename)era originario di kshe fu aggiunto a bashda Bash-2.02.


1
cat filenamefallirà se il nome del file inizia con un trattino perché cat accetta le opzioni. Puoi aggirare ciò sulla maggior parte dei sistemi moderni con cat -- filename.
Adam Katz,

-1

Pensa alla sostituzione dei comandi come se eseguissi un comando come al solito e scarica l'output nel punto in cui stai eseguendo il comando.

L'output dei comandi può essere utilizzato come argomento per un altro comando, per impostare una variabile e persino per generare l'elenco di argomenti in un ciclo for.

foo=$(echo "bar")imposterà il valore della variabile $foosu bar; l'output del comando echo bar.

Sostituzione comando


1
Credo che sia abbastanza chiaro dalla domanda che l'OP comprende le basi della sostituzione dei comandi; la domanda riguarda il caso speciale di $(< file), e non ha bisogno di un tutorial sul caso generale. Se stai dicendo che $(< file)è solo un caso normale $(command)con un comando di < file, allora stai dicendo la stessa cosa che dice Adam Katz , e tutti e due ti sbagli.
Scott,
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.