Bash-skriptfel med strängar med sökvägar som har mellanslag och jokertecken

Jag har problem med att få ner grunderna i Bash-skript. Här är vad jag har hittills:

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

Allt jag vill göra är att lista alla .txt filer i en for slinga så att jag kan göra saker med dem. Men utrymmet i my directory och asterisken i *.txt bara spelar inte snyggt. Jag försökte använda den med och utan dubbla citat, med och utan lockiga hängslen på variabla namn och kan fortfarande inte skriva ut alla .txt -filerna.

Detta är en väldigt grundläggande sak, men jag kämpar fortfarande för att jag är trött och inte kan tänka rakt.

Vad gör jag fel?

Jag har lyckats ansöka skriptet ovan om mina FILER inte har ett mellanslag eller en asterisk … Jag var tvungen att experimentera med eller utan att använda dubbla citat och hängslen för att få det att fungera. Men i det ögonblick jag har både mellanslag och en asterisk förstör det allt.

Svar

Inne i citat, * expanderar inte till en lista med filer. För att använda ett sådant jokertecken framgångsrikt måste det vara utanför citat.

Även om jokertecknet expanderade skulle uttrycket "${FILES}" resultera i en enda sträng, inte en lista med filer.

Ett tillvägagångssätt som skulle fungera skulle vara:

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

I ovanstående filnamn med mellanslag eller annat svårt tecken kommer att hanteras korrekt.

Ett mer avancerat tillvägagångssätt kan använda bash-matriser:

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

I det här fallet FILES är en matris med filnamn. Föräldrarna kring definitionen gör det till en matris. Observera att * ligger utanför citat. Konstruktionen "${FILES[@]}" är ett specialfall: det kommer att expandera till en lista med strängar där varje sträng är ett av filnamnen. Filnamn med mellanslag eller andra svåra tecken kommer att hanteras korrekt.

Kommentarer

  • bra som fungerade
  • Det ’ det är värt att notera att om du ’ går igenom banor så här genom funktioner, måste du se till att du citerar variabeln på egen hand snarare än att sammanfoga det som en del av en större sträng: for f in "$DIR"/*.txt = fin for f in "$DIR/*.txt" = bryter

Svar

Medan matriser som visas av John1024 är mycket mer meningsfullt, här kan du också använda split + glob-operatören (lämnar en skalarvariabel som inte är citerad).

Eftersom du bara vill ha globdelen av den operatören måste du inaktivera 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

Vad du kan göra är att bara lämna jokertecken utanför citattecken.
Något som:
för a i ”filer med mellanslag” * ”. txt ”
gör
bearbetning
gjort
Om jokertecken själva expanderar till mellanslag, behöver du inte använda en fil per rad, som att använda ls -l för att skapa listan över filer och använd bash-läsning för att få varje fil.

Svar

Enradig lösning, (körs 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;

för ditt / OP: s fall, ändra "./" till "/home/john/my directory/"

Att använda 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;  

Ovanstående funktioner kan också uppnås på detta (rekommenderade) sätt:

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

"./": det här är den aktuella katalogen. ange en katalogsökväg.
-not -type d: här konfigurerar -not den för att hoppa över nästa nämnda typ, & nästa nämnda -type är d = kataloger, så det hoppar kataloger. Använd f istället för d för att hoppa över filer. Använd l istället för d för att hoppa över symlink-filer.
-maxdepth 1: konfigurerar den för att bara hitta filer inom aktuell (aka: one) katalognivå. För att hitta filen i varje & första underkatalognivå, ställ maxdepth till 2. Om -maxdepth inte används, kommer den att söka rekursivt (inuti underkataloger), etc.
-iname "*.jpg": här konfigurerar -iname den för att hitta filer och ignorera (övre / nedre) -låda i filnamn / tillägg. -name ignorerar inte fallet. -lname hittar symlänkar. etc.
-print0: det skriver ut sökvägen för den aktuella filen till standardutmatning, följt av ett ASCII NUL-tecken (teckenkod 0), som vi kommer att senare upptäcka med read i while.
IFS=: här används det om ett filnamn slutar med ett mellanslag. Vi använder NUL / "" / \0 med IFS för att upptäcka varje hittat filnamn. Eftersom ” find ” är konfigurerad för att separera dem med \0 som produceras av -print0.
read -r -d $"\0" fileName: $"\0" är "" / NUL. -r användes om ett filnamn har en snedstreck.
läs [-ers] [-a aname] [-d avgränsa] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [namn. ..] […]
-r Backslash fungerar inte som en escape-karaktär. Backslash anses vara en del av linjen. I synnerhet får ett backslash-newline-par inte användas som en linjefortsättning.
done < <(...): Processbyte används här för att skicka / rörutgång från ” hitta ” till ” läs ” av ” medan ” -loop. Mer information: 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 andra svar av @ John1024 har han visat bra bash-baserad lösning som inte använder ” hitta ”, ett externt verktyg.
” hitta ” är mycket effektiv & snabbt, jag föredrar det när det finns för många filer att hantera.

i @ John1024s lösning kommer den att skriva ut matchande regelrad när det inte finns någon fil i katalogen, så under [ ! -e "${f}" ]... används linjen för att hoppa över den,
här är enradig lösning att använda i Terminal direkt:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Här är ett 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;  

Obs: om katalogen i DIR har "/" snedstreck (katalogindikator) i slutet, då i matchningsregeln , igen med "/" är inte nödvändigt,
Eller gör motsatt: i DIR ska du inte använda "/" i slutet och använd det i matchningsregeln "$DIR"/*.txt


Den extra kontrollen med [ ! -e "${f}" ]... kod kan undvikas, om nedan shell-option (aka: ” shopt ”) används eller aktiverat:
shopt -s nullglob

Om ett shopt-tillstånd ändrades av ett skript, skapar det i andra bash-baserade skriptprogram oväntade / oväntade problem.

Att ha konsekvent beteende i alla skript som använder bash, bash-shell-option ”s tillstånd ska spelas in / sparas i ditt skript, och när du är klar med att använda dina primära funktioner i ditt skript, ska det shell-alternativet återställas till tidigare inställningar.

Vi använder backtick (aka: grave-accent, aka: backquote) `...` kommandosubstitution (för interna bash-kommandokoder, etc), för att inte skapa ett nytt underskal, behålla bokstavlig betydelse av bakåtvänd snedstreck, för bredare stöd (aka: portabilitet), etc, Som backtick-baserade interna bash-kommandon, etc kan ofta köras i samma skal som skriptet, etc, Och så är det lite snabbare & bättre och också bättre för det ändamål som vi hanterar här. Om du föredrar $(...) kommandosubstitution använder du det, vem som helst har frihet & rätt att välja vad de föredrar, undvika etc. Mer info : här .

Så ovanstående skript visas igen, & den här gången med tidigare inställningar för en shopt återställd innan man avslutar skriptet:

 #!/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 av shopt -p shoptName (till exempel: shopt -p dotglob) kan vara
antingen detta: shopt -u shoptName (u är unset / deaktiverad / av / 0)
eller detta: shopt -s shoptName ( s är set / enabled / on / 1)
Positionen i bokstaven "s" eller "u" är alltid vid 7 (eftersom, i bash, en sträng ”s bokstavsposition börjar från 0, det vill säga den första bokstaven i en sträng är vid position 0)
Vi kan få detta "u" eller och lagra den i en variabel, så att vi kan använda den för att återställa tidigare tillstånd.
Och om vi tillämpar detta (nämns ovan) för att spara / återställa shopt-tillstånd, så kan vi undvik att använda externt verktyg "grep".

För att visa "txt" -fil som börjar med ".", det vill säga för att visa dold "txt" -fil, måste vi aktivera "dotglob" shopt.

Så den här gången nedan ingår "dotglob" & aktiverat för att visa 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 finns enklare sätt att spara / återställa shopt alternativ / värde.
Isaac postade här , hur man sparar + återställer Env / Shopt-variabel / optionstillstånd / värde.

Sparar shopt-tillstånd för ” nullglob ”:

... # your primary-function codes/commands, etc lines
Återställer tidigare shoptillstånd för ” nullglob ”, innan skriptet avslutas:
eval "$p_nullglob" ;

Flera shopt-tillstånd kan sparas på detta sätt:
p_multipleShopt="`shopt -p nullglob dotglob`";
och återställningsprocessen är densamma som tidigare:
eval "$p_multipleShopt" ;

Spara ALLA shopt-tillstånd på detta sätt:
p_allShopt="`shopt -p`";
och återställningsprocessen är densamma som tidigare:
eval "$p_allShopt" ;

Så här är en annan bash-baserad 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;  

Att använda eval är säkert ovan, eftersom variabeln "$p_allShopt" inte innehåller data som tillhandahålls av en användare eller data som inte är sanitized, That var håller utdata från det interna bash-kommandot shopt.
Om du fortfarande vill undvika eval använd nedan så 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;  

Få (andra) anmärkningsvärda & relaterad SHOPT som kan vara användbar, är:

  • nocaseglob: Om inställd matchar Bash filnamn i en skiftlägeskänsligt sätt när du utför filnamnsexpansion.
  • nocasematch: Om den är inställd matchar Bash mönster på ett skiftlägeskänsligt sätt när du utför matchning medan case eller [[ villkorliga kommandon, när du utför ordutvidgningar för mönstersubstitution, eller när du filtrerar möjliga kompletteringar som en del av programmerbar slutförande.
  • dotglob: Om inställt innehåller Bash filnamn som börjar med div id = ”1ceaafe854”> i resultaten av filnamnsexpansion. Filnamnen ‘.’ och ‘..’ måste alltid matchas uttryckligen, även om dotglob är inställt.
  • nullglob: Om inställt , Bash tillåter filnamnmönster som inte matchar några filer att expanderas till en nullsträng snarare än sig själva.
  • extglob: Om inställd kan de utökade mönstermatchningsfunktionerna beskrivna ovan (se Mönstermatchning ) är aktiverade.
  • globstar: Om inställt kommer mönstret ‘**’ som används i ett filnamnsexpansionskontext att matcha alla filer och noll eller fler kataloger och underkataloger. Om mönstret följs av en ‘/’ matchar bara kataloger och underkataloger.

Svar

När du vill bearbeta på en uppsättning filer, tänker du, vid deras namn kan det vara utrymme eller annan scape-kod kommer att finnas där, så innan du börjar din process som for loop eller find command ställer in IFS bash env variable till:

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

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *