Eroare script Bash cu șiruri cu căi care au spații și metacaractere

Am probleme cu obținerea elementelor de bază ale scripturilor Bash. Iată ce am până acum:

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

Tot ce vreau să fac este să listez toate fișierele .txt într-o buclă for, astfel încât să pot face lucruri cu ele. Dar spațiul din my directory și asteriscul din doar nu se joacă frumos. Am încercat să-l folosesc cu și fără ghilimele duble, cu și fără acolade pe nume variabile și totuși nu pot „tipări toate fișierele .txt.

Acesta este un lucru foarte de bază, dar încă mă lupt pentru că sunt obosit și nu pot gândi bine.

Ce fac greșit?

Am reușit să aplic cu succes scenariul de mai sus dacă DOSARELE mele nu au un spațiu sau un asterisc … A trebuit să experimentez cu sau fără utilizarea ghilimelelor duble și a acoladelor pentru ca acesta să funcționeze. Dar în momentul în care am ambele spații și un asterisc, încurcă totul.

Răspunde

În ghilimele, * nu se va extinde la o listă de fișiere. Pentru a utiliza cu succes o astfel de comodină, aceasta trebuie să fie în afara ghilimelelor.

Chiar dacă wildcard-ul s-a extins, expresia "${FILES}" ar rezulta într-un singur șir, nu o listă de fișiere.

O abordare care ar funcționa ar fi:

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

În cele de mai sus, numele fișierelor cu spații sau altele dificile caracterele vor fi tratate corect.

O abordare mai avansată ar putea folosi matrice bash:

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

În acest caz, FILES este o matrice de nume de fișiere. Parenții din jurul definiției o fac o matrice. Rețineți că * este în afara ghilimelelor. Construcția "${FILES[@]}" este un caz special: se va extinde la o listă de șiruri în care fiecare șir este unul dintre numele fișierelor. Numele de fișiere cu spații sau alte caractere dificile vor fi tratate corect.

Comentarii

  • minunat care a funcționat
  • It ‘ merită remarcat faptul că, dacă ‘ treceți astfel de căi prin funcții, trebuie să vă asigurați că citați variabila pe cont propriu mai degrabă decât să o concatenăm ca parte a unui șir mai mare: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = pauze

Răspuns

În timp ce utilizarea matricelor așa cum arată John1024 are mult mai mult sens, aici puteți folosi și operatorul split + glob (părăsind o variabilă scalară necotată).

Deoarece doriți doar partea glob a acelui operator, trebuie să dezactivați partea împărțit :

#! /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 

Răspuns

Ce puteți face este să lăsați numai caracterele wildcard în afara ghilimelelor.
Ceva de genul:
pentru un în „fișiere cu spații” * „. txt „
face
procesare
făcut
Dacă metacaracterele se extind în spații, atunci va trebui să nu folosiți o abordare fișier pe linie, cum ar fi folosiți ls -l pentru a genera lista de fișiere și utilizați bash read pentru a obține fiecare fișier.

Răspuns

Soluție bazată pe o singură linie, (pentru a rula în Terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

pentru cazul dvs. / OP, modificați "./" în "/home/john/my directory/"

De utilizat într-un fișier 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;  

Funcționalitatea de mai sus poate fi realizată și în acest mod (recomandat):

 #!/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;  

Scurt / scurt DESCRIERE:

"./": acesta este directorul curent. specificați o cale de director.
-not -type d: aici -not îl configurează pentru a sări peste următorul tip menționat, & următorul menționat -type este d = directoare, deci va sări directoare. Folosiți f în loc de d pentru a sări peste fișiere. Utilizați l în loc de d pentru a sări peste fișierele de linkuri simbolice.
-maxdepth 1: îl configurează pentru a găsi fișiere numai la nivelul directorului curent (aka: one). Pentru a găsi fișierul în fiecare & primul nivel de subdirector, setați maxdepth la 2. Dacă -maxdepth nu este utilizat, atunci va căuta recursiv (în interiorul subdirectoarelor) etc. /> -iname "*.jpg": aici -iname îl configurează pentru a găsi fișiere și a le ignora (sus / jos) -caz în nume de fișier / extensie. -name nu ignoră majuscule. -lname găsește legături simbolice. etc.
-print0: tipărește calea fișierului curent la ieșirea standard, urmată de un caracter ASCII NUL (cod caracter 0), pe care îl vom detectați ulterior utilizând read în while.
IFS=: aici este folosit în cazul în care un nume de fișier se termină cu un spațiu. Folosim NUL / "" / \0 cu IFS pentru a detecta fiecare nume de fișier găsit. Deoarece ” găsi ” este configurat pentru a le separa cu \0 care este produs de -print0.
read -r -d $"\0" fileName: $"\0" este "" / NUL. -r a fost utilizat în cazul în care un nume de fișier are o bară inversă.
citiți [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nume. ..] […]
-r Backslash nu acționează ca un personaj de evadare. Bară inversă este considerată a face parte din linie. În special, o pereche backslash-newline nu poate fi utilizată ca o continuare a liniei.
done < <(...): Proces-înlocuire utilizat aici pentru a trimite / ieșirea țevii ” găsiți ” în ” citiți ” din ” în timp ce ” -loop. Mai multe informații: 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


În alt răspuns de @ John1024, el a arătat o soluție excelentă bazată pe bash, care nu folosește ” find „, un utilitar extern.
” find ” este foarte eficient & rapid, îl prefer atunci când există prea multe fișiere de gestionat.

în soluția @ John1024 va imprima linia de potrivire-regulă atunci când nu există fișier în director, așa că sub [ ! -e "${f}" ]... linia este utilizată pentru a sări peste asta,
aici este soluția cu o singură linie pentru a fi utilizată direct în Terminal:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Iată un 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;  

Notă: dacă directorul din DIR are "/" bară (indicator director) la sfârșit, atunci în regula de potrivire , din nou utilizarea "/" nu este necesară,
Sau, faceți opus: în DIR nu utilizați "/" la sfârșit și deci folosiți-l în regula de potrivire "$DIR"/*.txt


Această verificare suplimentară utilizând [ ! -e "${f}" ]... codul poate fi evitat, dacă sub shell-option (aka: ” shopt „) este utilizat sau activat:
shopt -s nullglob

Dacă starea shopt a fost modificată de un script, atunci în alt program de script bazat pe bash creează probleme neașteptate / neprevăzute.

Pentru a avea un comportament consecvent în toate scripturile care utilizează starea bash, bash-shell-option ar trebui să fie înregistrată / salvată în scriptul dvs. și după ce ați terminat de utilizat funcțiile principale din script, atunci această opțiune shell ar trebui să fie readusă la setările anterioare.

Folosim backtick (aka: grave-accent, aka: backquote) `...` înlocuirea comenzii (pentru coduri de comandă bash interne etc.), pentru a nu genera o nouă sub-shell, păstrează semnificația literală a backslash, pentru suport mai larg (aka: portabilitate), etc, deoarece comenzile bash interne bazate pe backtick, etc pot fi deseori executate în același shell ca și scriptul, etc, și astfel este puțin mai rapid = „c4609ee59c”>

mai bine și, de asemenea, mai bine pentru scopul pe care îl gestionăm aici. Dacă preferați$(...)comanda-înlocuire, utilizați acest lucru, oricine are libertatea & de a alege ce preferă, de a evita, etc. Mai multe informații : aici .

Deci, scriptul de mai sus este din nou afișat, & de data aceasta cu setările anterioare ale unui magazin restaurat, înainte de a termina scriptul:

 #!/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;  

Ieșirea shopt -p shoptName (de exemplu: shopt -p dotglob) poate fi
fie: shopt -u shoptName (u este unset / disabled / off / 0)
sau, acesta: shopt -s shoptName ( s este set / enabled / on / 1)
Poziția literei "s" sau "u" este întotdeauna la 7 (deoarece, în bash, un poziția literei șirului începe de la 0, adică prima literă a unui șir se află în poziția 0)
Noi poate obține acest "u" sau și stocați-o într-o variabilă, astfel încât să o putem folosi pentru a restabili starea anterioară.
Și dacă aplicăm acest mod (menționat mai sus) pentru a salva / restabili starea shopt, atunci putem evitați utilizarea instrumentului extern "grep".

Pentru a vizualiza fișierul "txt" care începe cu ".", adică pentru a vizualiza fișierul "txt" ascuns, trebuie să activăm "dotglob" shopt.

Așadar, de data aceasta mai jos, "dotglob" este inclus & activat pentru a afișa fișierele HIDDEN "txt"

 #!/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;  

Există o modalitate mai simplă de a salva / restaura shopt opțiune / valoare.
Isaac a postat aici , cum să salvați + restaurați Env / Variabila Shopt / starea opțiunii / valoarea.

Salvarea stării shopt a ” nullglob „:

... # your primary-function codes/commands, etc lines
Restaurarea stării anterioare a magazinului ” nullglob „, înainte de a ieși din script:
eval "$p_nullglob" ;

Mai multe stări shopt pot fi salvate în acest fel:
p_multipleShopt="`shopt -p nullglob dotglob`";
și procesul de restaurare este același ca înainte:
eval "$p_multipleShopt" ;

Salvați TOATE stările shopt în acest fel:
p_allShopt="`shopt -p`";
și procesul de restaurare este același ca înainte:
eval "$p_allShopt" ;

Deci, iată o altă soluție bazată pe 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;  

Utilizarea eval este sigură mai sus, deoarece variabila "$p_allShopt" nu deține date furnizate de un utilizator sau date care nu sunt- igienizat, acel var conține ieșirea comenzii interne bash shopt.
Dacă totuși doriți să evitați eval, utilizați mai jos astfel lution:

 #!/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;  

Puține (alte) notabile & legate SHOPT care pot fi utile, sunt:

  • nocaseglob: Dacă este setat, Bash se potrivește cu numele fișierelor într-un mod nedistins cu majuscule și minuscule atunci când se efectuează extinderea numelui de fișier.
  • nocasematch: dacă este setat, Bash se potrivește cu modelele într-un mod nedistribuitor cu majuscule când se execută potrivirea în timp ce se execută case sau [[ comenzi condiționale, atunci când efectuați extinderi de cuvinte de substituire a modelelor sau când filtrați posibilele completări ca parte a finalizării programabile.
  • dotglob: dacă este setat, Bash include nume de fișiere care încep cu un ‘.’ în rezultatele extinderii numelui de fișier. Numele de fișiere ‘.’ și ‘..’ trebuie întotdeauna potrivite în mod explicit, chiar dacă dotglob este setat.
  • nullglob: Dacă este setat , Bash permite modelelor de nume de fișiere care nu se potrivesc fișierelor să se extindă la un șir nul, mai degrabă decât la ele însele.
  • extglob: dacă este setat, caracteristicile extinse de potrivire a modelelor descrise mai sus (consultați Pattern Matching ) sunt activate.
  • globstar: dacă este setat, modelul ‘**’ utilizat într-un context de extindere a numelui de fișier se va potrivi cu toate fișierele și zero sau mai multe directoare și subdirectoare. Dacă modelul este urmat de un ‘/’, se potrivesc doar directoarele și subdirectoarele.

Răspuns

Când doriți să procesați pe un set de fișiere, considerați că la numele lor poate fi spațiu sau alt cod scape va fi acolo, Deci, înainte de a începe procesul, cum ar fi for loop sau find command setați IFS bash env variable la:

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

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *