Perché {1,2} stampato da un comando in $ () non è interpolato?


8

Sono in una directory in cui ho due file di testo:

$ touch test1.txt
$ touch test2.txt

Quando provo ad elencare i file (con Bash) usando un pattern funziona:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Tuttavia, quando un modello è prodotto da un comando racchiuso in $(), solo uno dei modelli funziona:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

Cosa sta succedendo qui? Perché il modello {1,2}non funziona?


4
L'espansione della
parentesi

3
@SergiyKolodyazhnyy Il punto della domanda è che ?viene anche citato e si espande dopo $(...)averlo sostituito, ma l'espansione del controvento no.
Michael Homer,

1
@muru No, non è lo stesso problema. Qui l' ordine delle espansioni non ha importanza, ciò che conta è quale espansione ha luogo in quale contesto. Non sarei sorpreso se questa domanda fosse un duplicato, ma non riuscivo a trovarla.
Gilles 'SO- smetti di essere malvagio' il

1
@mosvy Ksh e bash eseguono espansioni nello stesso ordine, ma ksh rinforza l'espansione in un caso in cui bash non lo fa affatto. Zsh-with-globsubst fa le stesse espansioni di bash, ma in un ordine diverso.
Gilles 'SO- smetti di essere malvagio' il

1
@Gilles no, non lo fanno. Come documentato e facilmente dimostrato, ksh (e zsh) eseguirà l'espansione del controvento appena prima del movimento. zsh-con-globsubst non eseguirà alcuna espansione delle graffe affatto sui risultati $-expansions: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
mosvy

Risposte:


17

È una combinazione di due cose. Innanzitutto, l'espansione delle parentesi graffe non è un modello che corrisponde ai nomi dei file: è una sostituzione puramente testuale - vedi Qual è la differenza tra `a [bc] d` (parentesi) e` a {b, c} d` (parentesi graffe)? . In secondo luogo, quando si utilizza il risultato di una sostituzione di comando al di fuori delle doppie virgolette ( ls $(…)), ciò che accade è solo la corrispondenza dei modelli (e la suddivisione delle parole: l'operatore "split + glob"), non un riepilogo completo.

Con ls $(echo 'test?.txt'), il comando echo 'test?.txt'genera la stringa test?.txt(con una nuova riga finale). La sostituzione del comando genera la stringa test?.txt(senza una nuova riga finale, poiché la sostituzione del comando rimuove le righe successive). Questa sostituzione non quotata viene suddivisa in parole, producendo un elenco costituito da una singola stringa test?.txtpoiché non contiene caratteri di spazi bianchi (più precisamente, nessun carattere $IFS). Ogni elemento di questo elenco di un elemento subisce quindi l'espansione jolly condizionale e poiché ?nella stringa è presente un carattere jolly, si verifica l'espansione jolly. Poiché il modello test?.txtcorrisponde almeno a un nome file, l'elemento list test?.txtviene sostituito dall'elenco dei nomi file che corrispondono ai pattern, producendo l'elenco a due elementi contenentetest1.txte test2.txt. Alla fine lsviene chiamato con due argomenti test1e test2.

Con ls $(echo 'test{1,2}'), il comando echo 'test{1,2}'genera la stringa test{1,2}(con una nuova riga finale). La sostituzione del comando risulta nella stringa test{1,2}. Questa sostituzione non quotata viene suddivisa in parole, producendo un elenco costituito dalla singola stringa test{1,2}. Ogni elemento di questo elenco di un elemento viene quindi sottoposto a espansione jolly condizionale, che non fa nulla (l'elemento viene lasciato così com'è) poiché nella stringa non sono presenti caratteri jolly. Così lsviene chiamato con il singolo argomento test{1,2}.

Per confronto, ecco cosa succede ls $(echo test{1,2}). Il comando echo test{1,2}genera la stringa test1 test2(con una nuova riga finale). La sostituzione del comando genera la stringa test1 test2(senza una nuova riga finale). Questa sostituzione non quotata viene suddivisa in parole, producendo due stringhe test1e test2. Quindi, poiché nessuna delle stringhe contiene un carattere jolly, vengono lasciate sole, quindi lsviene chiamata con due argomenti test1e test2.


3
Si noti che pdksh e ksh93 eseguono l'espansione del controvento durante le espansioni (prima del globbing; non con noglob, ma nel caso di ksh93, ancora quando braceexpand è spento!)
Stéphane Chazelas,

Sembra che tu abbia dimenticato la .txtseconda spiegazione.
dice Val Reinstate Monica il

10

L'ordine delle espansioni è: espansione delle parentesi graffe; espansione della tilde, espansione di parametri e variabili, espansione aritmetica e sostituzione dei comandi (eseguita da sinistra a destra); divisione delle parole; e l'espansione del nome file.

L'espansione del controvento non avverrà dopo la sostituzione del comando. Puoi usare eval per forzare un altro round di espansione:

eval echo $(echo '{1,2}lala')

Il risultato è:

1lala 2lala

6

Questo problema è molto specifico bashed è perché hanno deciso bashdi separare l'espansione del controvento dall'espansione del nome file (globbing) e di eseguirlo prima, prima di tutte le altre espansioni.

Dalla bashmanpage:

L'ordine delle espansioni è: espansione delle parentesi graffe; espansione della tilde, espansione di parametri e variabili, espansione aritmetica e sostituzione dei comandi (eseguita da sinistra a destra); divisione delle parole; e l'espansione del nome percorso.

Nel tuo esempio, bashvedrai le parentesi graffe solo dopo aver eseguito la sostituzione del comando (il $(echo ...)), quando è troppo tardi.

Questo è diverso da tutti gli altri shell, che eseguono l'espansione del controvento appena prima (e alcuni anche come parte di) l'espansione del percorso (globbing). Ciò include ma non si limita a cshdove sono state inventate per la prima volta le espansioni del tutore.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

Quest'ultimo esempio è uguale a csh, zsh, ksh93, mksho fish.

Si noti inoltre che l'espansione del controvento come parte del globbing è disponibile anche tramite la glob(3)funzione di libreria (almeno su Linux e tutti i BSD) e in altre implementazioni indipendenti (ad es. In perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Perché ciò sia stato fatto diversamente bashha probabilmente una storia dietro, ma FWIW non sono riuscito a trovare alcuna spiegazione logica e trovo tutte le razionalizzazioni post-hoc non convincenti.


3
Nota che era perlsolito invocare cshper espandere globs, quindi non sorprende che riconosca ancora gli stessi operatori globbing dicsh
Stéphane Chazelas,

1

Vi preghiamo di provare:::

ls $ (test echo {1,2} \. txt)

Con una barra rovesciata. Ora funziona. Rimuovi anche le virgolette come diceva il poster precedente. Il punto non è per lo schema di corrispondenza, ma deve essere preso letteralmente come Periodo qui.


(1) La domanda chiede "Cosa sta succedendo qui? Perché [lo] schema {1,2}”si comporta come fa? La domanda non pone "Come posso ottenere un comando usando {1,2}per comportarmi come funziona il comando ??" Stai rispondendo alla domanda sbagliata. (2) La barra rovesciata non ha nulla a che fare con essa. Il tuo comando funziona come fa perché hai rimosso le virgolette che erano nel comando nella domanda.
G-Man dice "Ripristina Monica" l'

0

Funziona se rimuovi le virgolette

$ ls $(echo test{1,2})
test1  test2

9
L'espansione sta avvenendo prima della sostituzione del comando, che non credo sia la domanda che si pone (confronta cosa sta succedendo ?).
Michael Homer,
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.