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
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")
for f in "$DIR"/*.txt
= finefor f in "$DIR/*.txt"
= interruzioni