Bash-skriptfeil med strenger med stier som har mellomrom og jokertegn

Jeg har problemer med å få det grunnleggende om Bash-skripting ned. Her er det jeg har så langt:

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

Alt jeg vil gjøre er å liste opp alle .txt filer i en for sløyfe slik at jeg kan gjøre ting med dem. Men plassen i my directory og stjernen i *.txt bare spiller ikke pent. Jeg prøvde å bruke den med og uten doble anførselstegn, med og uten krøllete bukseseler på variabelt navn, og kan fremdeles ikke skrive ut alle .txt -filene.

Dette er en veldig grunnleggende ting, men jeg sliter fortsatt fordi jeg er sliten og kan ikke tenke rett.

Hva gjør jeg galt?

Jeg har klart å søke skriptet ovenfor hvis FILENE mine ikke har mellomrom eller stjerne … Jeg måtte eksperimentere med eller uten bruk av doble anførselstegn og seler for å få det til å fungere. Men i det øyeblikket jeg har begge mellomrom og en stjerne, ødelegger det alt.

Svar

Inne i anførselstegn, * utvides ikke til en liste over filer. For å bruke et slikt jokertegn med suksess, må det være utenfor anførselstegn.

Selv om jokertegnet utvidet seg, vil uttrykket "${FILES}" resultere i en enkelt streng, ikke en liste over filer.

En tilnærming som ville fungere, ville være:

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

I det ovenstående kan filnavn med mellomrom eller annet vanskelig tegn vil bli håndtert riktig.

En mer avansert tilnærming kan bruke bash-matriser:

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

I dette tilfellet FILES er en rekke filnavn. Parensene rundt definisjonen gjør det til et utvalg. Merk at * er utenfor anførselstegn. Konstruksjonen "${FILES[@]}" er et spesielt tilfelle: det utvides til en liste over strenger der hver streng er et av filnavnene. Filnavn med mellomrom eller andre vanskelige tegn blir håndtert riktig.

Kommentarer

  • flott som fungerte
  • Det ‘ det er verdt å merke seg at hvis du ‘ går forbi stier som dette gjennom funksjoner, må du sørge for at du siterer variabelen alene i stedet for å sammenkoble den som en del av en større streng: for f in "$DIR"/*.txt = fin for f in "$DIR/*.txt" = pauser

Svar

Mens det er mye mer fornuftig å bruke arrays som vist av John1024, kan du her også bruke split + glob-operatøren (forlater en skalarvariabel ikke sitert).

Siden du bare vil ha glob-delen av den operatøren, må du deaktivere split delen:

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

Svar

Det du kan gjøre er å la bare jokertegn stå utenfor anførselstegn.
Noe som:
for a i «filer med mellomrom» * «. txt «
gjør
bearbeiding
gjort
Hvis jokertegnene selv utvides til mellomrom, må du ikke bruke en fil per linje-tilnærming, som bruk ls -l for å generere listen over filer og bruk bash-lesing for å få hver fil.

Svar

Enkelinjebasert løsning, (for å kjøre i 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;

for ditt / OPs tilfelle, endre "./" til "/home/john/my directory/"

For bruk i en skriptfil:

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

Over funksjonalitet kan også oppnås på denne (anbefalte) måten:

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

Kort / kort BESKRIVELSE:

"./": dette er den nåværende katalogen. spesifiser en katalogbane.
-not -type d: her konfigurerer -not den for å hoppe over neste nevnte type, & neste nevnte -type er d = kataloger, så den hopper kataloger. Bruk f i stedet for d for å hoppe over filer. Bruk l i stedet for d for å hoppe over symlink-filer.
-maxdepth 1: konfigurerer den til å finne filer innen det nåværende (aka: one) katalognivået. For å finne filen i hvert & første underkatalognivå, sett maxdepth til 2. Hvis -maxdepth ikke brukes, vil den søke rekursivt (i underkataloger) osv.
-iname "*.jpg": her konfigurerer -iname den for å finne filer og ignorere (øvre / nedre) -boksen i filnavn / utvidelse. -name ignorerer ikke tilfelle. -lname finner symlenker. etc.
-print0: den skriver ut banenavnet til den nåværende filen til standardutdata, etterfulgt av et ASCII NUL-tegn (tegnkode 0), som vi vil oppdag senere ved å bruke read i while.
IFS=: her brukes den i tilfelle et filnavn slutter med mellomrom. Vi bruker NUL / "" / \0 med IFS for å oppdage hvert funnet filnavn. Som » find » er konfigurert til å skille dem med \0 som er produsert av -print0.
read -r -d $"\0" fileName: $"\0" er "" / NUL. -r ble brukt i tilfelle filnavnet har tilbakeslag.
les [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name. ..] […]
-r Backslash fungerer ikke som en fluktfigur. Backslash anses å være en del av linjen. Spesielt kan et backslash-newline-par ikke brukes som en linjefortsetting.
done < <(...): Prosessubstitusjon brukt her for å sende / rørutgang fra » finn » til » les » av » mens » -loop. Mer info: 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


I andre svar av @ John1024 har han vist god bash-basert løsning, som ikke bruker » finn «, et eksternt verktøy.
» finn » er veldig effektivt & raskt, jeg foretrekker det når det er for mange filer å håndtere.

i løsningen på @ John1024 vil den skrive ut matching-regel linje når det ikke er noen fil i katalogen, så under [ ! -e "${f}" ]... linjen brukes til å hoppe over det,
her er en-linjeløsning å bruke i Terminal direkte:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Her er et skript:

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

Merk: hvis katalogen i DIR har "/" skråstrek (katalogindikator) på slutten, så i samsvarende regel , å bruke "/" er ikke nødvendig,
Eller gjør det motsatte: i DIR ikke bruk "/" på slutten og bruk den så i matching-rule "$DIR"/*.txt


Den ekstra kontrollen ved å bruke [ ! -e "${f}" ]... kode kan unngås, hvis under shell-option (aka: » shopt «) brukes eller aktivert:
shopt -s nullglob

Hvis en shopt-tilstand ble endret av et skript, skaper det i andre bash-baserte skriptprogrammer uventede / uventede problemer.

Å ha jevn oppførsel på tvers av alle skript som bruker tilstanden til bash, bash-shell-option skal registreres / lagres i skriptet ditt, og når du er ferdig med å bruke de primære funksjonene i skriptet ditt, skal det shell-alternativet settes tilbake til tidligere innstillinger.

Vi bruker backtick (aka: grave-accent, aka: backquote) `...` kommandosubstitusjon (for interne bash-kommandokoder, etc), for ikke å gyte et nytt underskall, beholde bokstavelig betydning av tilbakeslag, for bredere støtte (aka: portabilitet) osv. Som backtick-baserte interne bash-kommandoer osv. kan ofte utføres i samme skall som skriptet, osv. Og så er det litt raskere & bedre, og også bedre for det formålet vi håndterer her. Hvis du foretrekker $(...) kommandosubstitusjon, så bruk det, hvem som helst har frihet & rett til å velge hva de foretrekker, unngå osv. Mer info : her .

Så ovenstående skript vises igjen, & denne gangen med tidligere innstillinger for en shopp gjenopprettet, før skriptet avsluttes:

 #!/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 of shopt -p shoptName (for eksempel: shopt -p dotglob) kan være
, dette: shopt -u shoptName (u er unset / deaktivert / av / 0)
eller dette: shopt -s shoptName ( s er set / aktivert / på / 1)
Posisjonen av bokstaven "s" eller "u" er alltid på 7 (fordi, i bash, en streng «s bokstavposisjon begynner fra 0, det vil si at første bokstav i en streng er i posisjon 0)
Vi kan få dette "u" eller og lagre den i en variabel, slik at vi kan bruke den til å gjenopprette forrige tilstand.
Og hvis vi bruker denne (nevnt ovenfor) måte å lagre / gjenopprette shopt-tilstand, så kan vi unngå å bruke eksternt verktøy "grep".

For å se "txt" fil som begynner med ".", det vil si for å se skjult "txt" -fil, må vi aktivere "dotglob" shopt.

Så denne gangen nedenfor er "dotglob" inkludert & aktivert for å vise HIDDEN "txt" filer:

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

Det er enklere å lagre / gjenopprette shopt alternativ / verdi.
Isaac la ut her , hvordan du lagrer + gjenoppretter Env / Shopt-variabel / alternativstatus / verdi.

Lagrer shopt-tilstand for » nullglob «:

... # your primary-function codes/commands, etc lines
Gjenoppretter tilbake forrige shoptilstand for » nullglob «, før du avslutter skriptet:
eval "$p_nullglob" ;

Flere shopt-stater kan lagres på denne måten:
p_multipleShopt="`shopt -p nullglob dotglob`";
og gjenopprettingsprosessen er den samme som før:
eval "$p_multipleShopt" ;

Lagre ALLE shopt-tilstander på denne måten:
p_allShopt="`shopt -p`";
og gjenopprettingsprosessen er den samme som før:
eval "$p_allShopt" ;

Så her er en annen bash-basert løsning:

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

Bruk av eval er trygt ovenfor, da variabelen "$p_allShopt" ikke inneholder data gitt av en bruker eller data som ikke er sanitized, That var holder output av bash intern kommando shopt.
Hvis du fortsatt vil unngå eval så bruk nedenfor så lusjon:

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

Få (annet) bemerkelsesverdige & relatert SHOPT som kan være nyttige, er:

  • nocaseglob: Hvis angitt, samsvarer Bash med filnavn i en store og små bokstaver når du utfører filnavnutvidelse.
  • nocasematch: Hvis angitt, samsvarer Bash med mønstre på en ikke-sensitiv måte når du utfører matching mens du kjører case eller [[ betingede kommandoer, når du utfører ordutvidelser for mønstersubstitusjon, eller når du filtrerer mulige fullføringer som en del av programmerbar fullføring.
  • dotglob: Hvis angitt, inkluderer Bash filnavn som begynner med en ‘.’ i resultatene av filnavnutvidelse. Filnavnene ‘.’ og ‘..’ må alltid matches eksplisitt, selv om dotglob er satt.
  • nullglob: Hvis angitt , Lar Bash filnavnmønstre som ikke samsvarer med noen filer utvides til en nullstreng, snarere enn seg selv.
  • extglob: Hvis angitt, vil de utvidede mønstermatchingsfunksjonene beskrevet ovenfor (se Pattern Matching ) er aktivert.
  • globstar: Hvis angitt, vil mønsteret ‘**’ som brukes i en filutvidelseskontekst, matche alle filer og null eller flere kataloger og underkataloger. Hvis mønsteret følges av en ‘/’, er det bare kataloger og underkataloger som samsvarer.

Svar

Når du ønsker å behandle på sett med filer, vurderer du at navnet deres kan være mellomrom eller annen scape-kode vil være der, så før du starter prosessen, for eksempel for loop eller find command sett IFS bash env variable til:

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

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *