Perché awk si ferma e aspetta se il nome file contiene = e come aggirare questo?


Risposte:


19

Come dice Chris , gli argomenti del modulo variablename=anythingvengono trattati come assegnazione di variabili (che vengono eseguiti nel momento in cui gli argomenti vengono elaborati rispetto a quelli (più recenti) -v var=valueeseguiti prima delle BEGINistruzioni) anziché come nomi di file di input.

Questo può essere utile in cose come:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Dove è possibile specificare un diverso FS/ RSper file. È anche comunemente usato in:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Quale è una versione più sicura di:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(che non funziona se file1è vuoto)

Ma questo si intromette quando hai file il cui nome contiene =caratteri.

Ora, questo è solo un problema quando ciò che rimane del primo =è un awknome di variabile valido .

Ciò che costituisce un nome di variabile valido in awkè più rigoroso di in sh.

POSIX richiede che sia simile a:

[_a-zA-Z][_a-zA-Z0-9]*

Con solo caratteri del set di caratteri portatile. Tuttavia, il /usr/xpg4/bin/awkSolaris 11 almeno non è conforme a tale riguardo e consente qualsiasi carattere alfabetico nella locale in nomi di variabili, non solo a-zA-Z.

Quindi un argomento come x+y=fooo =baro ./foo=barè ancora trattato come un nome di file di input e non come un'assegnazione poiché ciò che rimane del primo =non è un nome di variabile valido. Un argomento come Stéphane=Chazelas.txtpuò o no, a seconda awkdell'implementazione e delle impostazioni internazionali.

Ecco perché con awk, si consiglia di utilizzare:

awk '...' ./*.txt

invece di

awk '...' *.txt

per esempio per evitare il problema se non puoi garantire che il nome dei txtfile non contenga =caratteri.

Inoltre, -vfoo=bar.txtfai attenzione che un argomento simile può essere trattato come un'opzione se usi:

awk -f file.awk -vfoo=bar.txt

(vale anche per awk '{code}' -vfoo=bar.txtle awkversioni from busybox precedenti alla 1.28.0, consultare la relativa segnalazione di bug ).

Ancora una volta, usare ./*.txtaggira quello (usare un ./prefisso aiuta anche con un file chiamato -che altrimenti awkcapisce invece come input standard ).

Questo è anche il motivo

#! /usr/bin/awk -f

le shebang non funzionano davvero. Mentre var=valuequelli possono essere aggirati fissando i ARGVvalori (aggiungi un ./prefisso) in BEGINun'istruzione:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Ciò non aiuta con le opzioni come quelle viste awke non dalla awksceneggiatura.

Un potenziale problema estetico con l'uso di quel ./prefisso è che finisce FILENAME, ma puoi sempre usarlo substr(FILENAME, 3)per toglierlo se non lo vuoi.

L'implementazione GNU di awkrisolve tutti questi problemi con la sua -Eopzione.

Dopodiché -E, gawk si aspetta solo il percorso dello awkscript (dove -significa ancora stdin) e quindi un elenco di soli percorsi dei file di input (e lì non -viene nemmeno trattato in modo speciale).

È appositamente progettato per:

#! /usr/bin/gawk -E

shebangs in cui l'elenco degli argomenti sono sempre file di input (tieni presente che sei ancora libero di modificare tale ARGVelenco in BEGINun'istruzione).

Puoi anche usarlo come:

gawk -e '...awk code here...' -E /dev/null *.txt

Usiamo -Econ uno script vuoto ( /dev/null) solo per assicurarci che quelli che *.txtseguono siano sempre trattati come file di input, anche se contengono =caratteri.


Non vedo come il percorso esplicito che finisce in FILENAME sia un problema. O lo script awk è generale, nel qual caso dovrebbe gestire tutti i tipi di percorsi che finiscono in FILENAME (inclusi ma non limitati a ../foo, /path/to/fooe percorsi che hanno una codifica diversa) - nel qual caso substr(FILENAME,3)non sarà sufficiente, oppure è uno script one-shot in cui l'utente conosce praticamente quali sono i nomi dei file - nel qual caso probabilmente non dovrebbe preoccuparsi di nessuno di essi contenente =nessuno dei due ;-)
mosvy,

2
@mosvy Non penso che affermi così tanto che ./è un problema, ma che potrebbe essere indesiderabile in determinate condizioni, come casi in cui il nome file deve essere incluso nell'output, nel qual caso ./dovrebbe essere ridondante e non necessario, quindi è necessario dovrò liberarmene in qualche modo. Ecco almeno un esempio . Per quanto riguarda l'utente che sa quali sono i nomi dei file - beh, in questo caso sappiamo anche cos'è il nome file, ma =ostacola comunque l'elaborazione corretta. Quindi può -mettersi in cammino.
Sergiy Kolodyazhnyy il

@mosvy, sì, l'idea è che tu voglia usare il ./prefisso per aggirare quella awk(mis) funzionalità ma poi finisci con un ./output in output che potresti voler eliminare. Vedi come verificare se la prima riga del file contiene una stringa specifica? come esempio.
Stéphane Chazelas il

Non è solo il locale (relativo a questa directory) ./ma anche il globale (percorso assoluto) /che rende awk interpretare l'argomento come un file.
Isaac,

21

Nella maggior parte delle versioni di awk, gli argomenti dopo l'esecuzione del programma sono:

  1. Un file
  2. Un incarico del modulo x=y

Dato che il tuo nome file viene interpretato come caso n. 2, awk sta ancora aspettando qualcosa da leggere su stdin (poiché non percepisce che è stato passato alcun nome file).

Portabilmente, questo comportamento è documentato in POSIX :

È possibile mescolare uno dei due seguenti tipi di argomento:

  • file: un percorso di un file che contiene l'input da leggere, che viene confrontato con l'insieme di schemi nel programma. Se non viene specificato alcun operando di file o se un operando di file è "-", deve essere utilizzato l'input standard.
  • assegnazione: un operando che inizia con un carattere di sottolineatura o alfabetico dal set di caratteri portatile (vedere la tabella nel volume Definizioni di base di IEEE Std 1003.1-2001, Sezione 6.1, Set di caratteri portatile), seguito da una sequenza di caratteri di sottolineatura, cifre, e gli alfabeti del set di caratteri portatile, seguito dal carattere '=', devono specificare un'assegnazione variabile anziché un nome percorso.

Come tale, portabilmente, hai alcune opzioni (il numero 1 è probabilmente il meno invadente):

  1. Usa awk ... ./my=file, che elude questo dato che .non è "un carattere di sottolineatura o alfabetico dal set di caratteri portatile".
  2. Metti il ​​file su stdin usando awk ... < my=file. Tuttavia, questo non funziona bene con più file.
  3. Crea temporaneamente un hardlink al file e usalo. Puoi fare qualcosa del genere ln my=file my_file, quindi usarlo my_filenormalmente. Non verrà eseguita alcuna copia ed entrambi i file saranno supportati dagli stessi dati e metadati inode. Dopo averlo usato, è sicuro rimuovere il collegamento creato poiché il numero di riferimenti all'inode sarà comunque maggiore di 0.

6
Non ./my=file funziona? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Questo dovrebbe essere portatile perché ./mynon è un nome di variabile valido, quindi non dovrebbe essere analizzato in questo modo.
Stephen Harris,

2
Come dice quel testo POSIX, il problema è solo quando il primo =è preceduto da un carattere di sottolineatura o alfabetico dal set di caratteri portatile (vedere la tabella nel volume Definizioni di base di IEEE Std 1003.1-2001, Sezione 6.1, Set di caratteri portatile), seguito da una sequenza di caratteri di sottolineatura, cifre e alfabeti dal set di caratteri portatile . quindi un percorso di file come ++foo=bar.txto =fooo ./foo=barsono tutti OK come quello .o +non è un [_a-zA-Z].
Stéphane Chazelas,

1
@SergiyKolodyazhnyy awk è esterno alla shell, quindi non importa quale usi. ./my=filesarà passato alla lettera.
Chris Down,

1
@SergiyKolodyazhnyy, lo stesso per awk '{print $1,$2}' /etc/passwd. Il punto è che avere la shell aperta il file invece di awk non fa alcuna differenza se lo rende ricercabile o meno. In realtà, awk '{exit}' < /etc/passwdti aspetteresti awkdi tornare alla fine del primo disco exitper assicurarti che lasci la posizione all'interno dello stdin. POSIX lo richiede. /usr/xpg4/bin/awklo fa su Solaris, ma gawkmawksembra né farlo su GNU / Linux.
Stéphane Chazelas il

3
@mosvy, vedere la sezione INPUT FILES su pubs.opengroup.org/onlinepubs/9699919799/utilities/… È utile in una serie di schemi di utilizzo che hanno senso solo con file regolari come quando si desidera troncare un file o scrivere dati al suo una posizione identificata in awkquesto modo.
Stéphane Chazelas il

3

Per citare la documentazione gawk (nota l'enfasi aggiunta):

Eventuali argomenti aggiuntivi sulla riga di comando vengono normalmente trattati come file di input da elaborare nell'ordine specificato. Tuttavia, un argomento che ha la forma var = value, assegna il valore value alla variabile var: non specifica affatto un file.

Perché il comando si ferma e aspetta? Perché nel modulo awk 'processing_script_here' my=file.txt non c'è alcun file specificato dalla definizione sopra - my=file.txtviene interpretato come assegnazione di variabili, e se non c'è alcun file definito awkleggerà stdin (anche evidente dal stracequale dimostra che awk in tale comando è in attesa su read(0,'...)syscall.

Questo è anche documentato nelle specifiche awk di POSIX , vedere la sezione OPERANDS e parte degli incarichi )

L'assegnazione delle variabili è evidente in awk '{print foo}' foo=bar /etc/passwdquanto il valore di fooè stampato per ogni riga in / etc / passwd. La specifica ./foo=baro il percorso completo tuttavia funziona.

Si noti che in esecuzione stracesu awk '1' foo=barcosì come il controllo con cat foo=barspettacoli che si tratta di problema awk-specifici, e execve non mostra il nome del file come argomento passato, in modo da gusci non hanno nulla a che fare con le assegnazioni variabile ENV in questo caso.

Inoltre, si noti che awk '...script...' foo=barnon causerà la creazione di variabili di ambiente tramite shell, poiché le assegnazioni di variabili di ambiente dovrebbero precedere un comando per avere effetto. Vedere le Regole di grammatura della shell POSIX , punto numero 7. Inoltre, questo può essere verificato tramiteawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

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.