Errore di script Bash con stringhe con percorsi che contengono spazi e caratteri jolly

Ho problemi ad apprendere le basi dello scripting Bash. Ecco quello che ho finora:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Tutto quello che voglio fare è elencare tutti i file .txt in un for ciclo così posso utilizzarli. Ma lo spazio in my directory e lasterisco in *.txt semplicemente non stanno giocando bene. Ho provato a usarlo con e senza virgolette doppie, con e senza parentesi graffe sui nomi delle variabili e non riesco ancora a “stampare tutti i file .txt.

Questo è un cosa molto semplice, ma sto ancora lottando perché sono stanco e non riesco a pensare chiaramente.

Cosa sto facendo di sbagliato?

Sono stato in grado di applicare con successo lo script sopra se i miei FILE non hanno uno spazio o un asterisco … ho dovuto sperimentare con o senza luso di virgolette doppie e parentesi graffe per farlo funzionare. Ma nel momento in cui ho sia gli spazi che un asterisco, tutto si incasina.

Risposta

Tra virgolette, il * non si espanderà in un elenco di file. Per utilizzare un carattere jolly con successo, deve essere al di fuori delle virgolette.

Anche se il carattere jolly si espandesse, lespressione "${FILES}" risulterebbe in una singola stringa, non un elenco di file.

Un approccio che potrebbe funzionare sarebbe:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

In precedenza, nomi di file con spazi o altri i caratteri verranno gestiti correttamente.

Un approccio più avanzato potrebbe utilizzare gli array bash:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

In questo caso, FILES è un array di nomi di file. Le parentesi che circondano la definizione ne fanno un array. Tieni presente che * non è compreso tra virgolette. Il costrutto "${FILES[@]}" è un caso speciale: si espanderà in un elenco di stringhe in cui ogni stringa è uno dei nomi di file. I nomi dei file con spazi o altri caratteri difficili verranno gestiti correttamente.

Commenti

  • fantastico che ha funzionato
  • ‘ vale la pena notare che se ‘ stai passando percorsi come questo attraverso le funzioni, devi assicurarti di citare la variabile da sola invece di concatenarlo come parte di una stringa più grande: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = interruzioni

Risposta

Mentre usare gli array come mostrato da John1024 ha molto più senso, qui puoi anche usare loperatore split + glob (lasciando una variabile scalare non quotata).

Dato che vuoi solo la parte glob di quelloperatore, devi disabilitare la parte split :

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Risposta

Quello che puoi fare è lasciare solo i caratteri jolly fuori dalle virgolette.
Qualcosa come:
per un in “file con spazi” * “. txt “
eseguire
elaborazione
fatto
Se i caratteri jolly stessi si espandono in spazi, allora non sarà necessario utilizzare un approccio file per riga, come utilizzare ls -l per generare lelenco dei file e usa bash read per ottenere ogni file.

Answer

Soluzione basata su riga singola, (da eseguire nel Terminale):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

per il tuo caso / OP “, cambia "./" in "/home/john/my directory/"

Da utilizzare in un file di script:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

Le funzionalità sopra possono essere ottenute anche in questo modo (consigliato):

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

DESCRIZIONE Breve / Breve:

"./": questa è la directory corrente. specificare un percorso di directory.
-not -type d: qui -not lo sta configurando per saltare il il tipo citato successivo, & -type d = directory, quindi salterà directory. Utilizza f invece di d per saltare i file. Utilizza l invece di d per saltare i file di collegamento simbolico.
-maxdepth 1: lo sta configurando per trovare i file solo allinterno del livello di directory corrente (aka: uno). Per trovare file allinterno di ogni & primo livello di sottodirectory, imposta maxdepth su 2. Se -maxdepth non viene utilizzato, eseguirà la ricerca ricorsiva (allinterno di sottodirectory), ecc. /> -iname "*.jpg": qui -iname lo sta configurando per trovare file e ignorarli (superiore / inferiore) -case nel nome del file / estensione. -name non ignora maiuscole e minuscole. -lname trova collegamenti simbolici. eccetera.
-print0: stampa il percorso del file corrente sullo standard output, seguito da un carattere ASCII NUL (codice carattere 0), che verrà visualizzato rilevare successivamente utilizzando read in while.
IFS=: qui viene utilizzato nel caso in cui il nome di un file termini con uno spazio. Stiamo utilizzando NUL / "" / \0 con IFS per rilevare ogni nome di file trovato. Poiché ” find ” è configurato per separarli con \0 prodotto da -print0.
read -r -d $"\0" fileName: $"\0" è "" / NUL. -r è stato utilizzato nel caso in cui il nome di un file abbia una barra rovesciata.
read [-ers] [-a aname] [-d delim] [-i testo] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nome. ..] […]
-r Backslash non funge da carattere di escape. Il backslash è considerato parte della linea. In particolare, una coppia backslash-newline non può essere utilizzata come continuazione di riga.
done < <(...): Process-Substitution utilizzato qui per inviare / pipe loutput di ” trova ” nella ” lettura ” di ” mentre ” -loop. Ulteriori informazioni: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


In unaltra risposta di @ John1024, ha mostrato unottima soluzione basata su bash, che non utilizza ” trova “, unutilità esterna.
” trova ” è molto efficace & veloce, lo preferisco quando ci sono troppi file da gestire.

nella soluzione di @ John1024 stamperà il riga della regola di corrispondenza quando non è presente alcun file nella directory, quindi sotto [ ! -e "${f}" ]... viene utilizzata la riga per ignorarla,
ecco una soluzione a riga singola da utilizzare direttamente nel terminale:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Ecco uno script:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Nota: se la directory in DIR ha "/" barra (indicatore di directory) alla fine, allora nella regola di corrispondenza , di nuovo non è necessario utilizzare "/",
Oppure fai il contrario: in DIR non utilizzare "/" alla fine quindi usalo nella regola di corrispondenza "$DIR"/*.txt


Quel controllo extra utilizzando [ ! -e "${f}" ]... può essere evitato, se inferiore a shell-option (aka: ” shopt “) è utilizzato o abilitato:
shopt -s nullglob

Se uno stato dello shopt è stato modificato da uno script, in un altro programma di script basato su bash crea problemi imprevisti / imprevisti.

Per avere un comportamento coerente in tutti gli script che utilizza bash, lo stato di bash-shell-option dovrebbe essere registrato / salvato allinterno del tuo script, e una volta che hai finito di usare le tue funzioni primarie nello script, allora quellopzione di shell dovrebbe essere ripristinata alle impostazioni precedenti.

Usiamo il backtick (aka: grave-accent, aka: backquote) `...` sostituzione del comando (per codici di comando bash interni, ecc.), per non generare una nuova shell secondaria, mantieni il significato letterale di backslash, per un supporto più ampio (aka: portabilità), ecc., poiché i comandi bash interni basati su backtick, ecc. possono essere spesso eseguiti nella stessa shell dello script, ecc., E quindi è un po più veloce & migliore e anche migliore per lo scopo che stiamo trattando qui. Se preferisci la $(...) sostituzione del comando, usala, chiunque ha la & diritto di scegliere ciò che preferisce, evitare, ecc. Altre informazioni : qui .

Quindi lo script precedente viene nuovamente mostrato, & questa volta con le impostazioni precedenti di un negozio ripristinato, prima di terminare lo script:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Output di shopt -p shoptName (ad esempio: shopt -p dotglob) può essere
questo: shopt -u shoptName (il u è unset / disabled / off / 0)
o, questo: shopt -s shoptName ( s è set / enabled / on / 1)
La posizione della lettera "s" o "u" è sempre in 7 (perché, in bash, un la posizione della lettera della stringa “s inizia da 0, ovvero la prima lettera di una stringa è nella posizione 0)
Noi può ottenere questo "u" o e memorizzarlo in una variabile, in modo che possiamo usarlo per ripristinare lo stato precedente.
E se applichiamo questo modo (menzionato sopra) per salvare / ripristinare lo stato di negozio, allora possiamo evitare di utilizzare lo strumento esterno "grep".

Per visualizzare il file "txt" che inizia con ".", ovvero, per visualizzare il file "txt" nascosto, è necessario abilitare "dotglob" shopt.

Quindi questa volta in seguito, "dotglob" è incluso & abilitato a mostrare i file "txt" nascosti:

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Esistono modi più semplici per salvare / ripristinare shopt opzione / valore.
Isaac ha pubblicato qui , come salvare + ripristinare Env / Variabile di negozio / stato / valore dellopzione.

Salvataggio dello stato di negozio di ” nullglob “:

... # your primary-function codes/commands, etc lines
Ripristino dello stato precedente di ” nullglob “, prima di uscire dallo script:
eval "$p_nullglob" ;

È possibile salvare più stati di acquisto in questo modo:
p_multipleShopt="`shopt -p nullglob dotglob`";
e il processo di ripristino è lo stesso di prima:
eval "$p_multipleShopt" ;

Salva TUTTI gli stati del negozio in questo modo:
p_allShopt="`shopt -p`";
e il processo di ripristino è lo stesso di prima:
eval "$p_allShopt" ;

Quindi ecco unaltra soluzione basata su bash:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Luso di eval è sicuro sopra, poiché la variabile "$p_allShopt" non contiene dati forniti da un utente o dati che non sono- disinfettato, Quella var contiene loutput del comando interno di bash shopt.
Se vuoi comunque evitare eval, usalo di seguito così luzione:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Pochi (altri) notevoli & correlati SHOPT che possono essere utili, sono:

  • nocaseglob: se impostato, Bash trova i nomi dei file in un modo senza distinzione tra maiuscole e minuscole quando si esegue lespansione del nome file.
  • nocasematch: se impostato, Bash trova i pattern senza distinzione tra maiuscole e minuscole durante lesecuzione di case o [[ comandi condizionali, quando si eseguono espansioni di parole di sostituzione di pattern o quando si filtrano possibili completamenti come parte del completamento programmabile.
  • dotglob: se impostato, Bash include nomi di file che iniziano con ‘.’ nei risultati dellespansione del nome file. I nomi di file ‘.’ e ‘..’ devono sempre essere abbinati in modo esplicito, anche se dotglob è impostato.
  • nullglob: se impostato , Bash consente ai modelli di nome file che non corrispondono a nessun file di espandersi in una stringa nulla, piuttosto che se stessi.
  • extglob: se impostato, le funzionalità di corrispondenza dei modelli estese descritte sopra (vedere Pattern Matching ) sono abilitati.
  • globstar: se impostato, il pattern ‘**’ utilizzato in un contesto di espansione del nome di file corrisponderà a tutti i file e zero o più directory e sottodirectory. Se il pattern è seguito da un ‘/’, solo le directory e le sottodirectory corrispondono.

Risposta

Quando vuoi elaborare su un set di file, considera che al loro nome potrebbe esserci uno spazio o un altro codice di scape sarà lì, quindi prima di iniziare il tuo processo come for loop o find command imposta IFS bash env variable su:

IFS=$(echo -en "\n\b") 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *