Chyba skriptu Bash s řetězci s cestami, které mají mezery a zástupné znaky

Mám potíže se získáním základů skriptování Bash. Tady je to, co zatím mám:

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

Jediné, co chci udělat, je vypsat všechny .txt soubory ve smyčce for, abych s nimi mohl dělat věci. Ale prostor v my directory a hvězdičce v *.txt prostě nehraje pěkně. Snažil jsem se jej použít s uvozovkami i bez nich, se složenými závorkami i bez nich na názvy proměnných a stále nemohu vytisknout všechny soubory .txt.

Toto je velmi základní věc, ale stále bojuji, protože jsem unavený a nemůžu myslet na rovinu.

Co dělám špatně?

Dokázal jsem se úspěšně přihlásit výše uvedený skript, pokud moje SOUBORY nemají mezeru nebo hvězdičku … musel jsem experimentovat s nebo bez použití uvozovek a složených závorek, aby to fungovalo. Ale v okamžiku, kdy mám obě mezery a hvězdičku, to všechno pokazí.

Odpověď

Uvnitř uvozovek je * se nerozbalí na seznam souborů. Chcete-li takový zástupný znak úspěšně použít, musí být mimo uvozovky.

I kdyby se zástupný znak rozšířil, výraz "${FILES}" by měl za následek jediný řetězec, nikoli seznam souborů.

Jeden přístup, který by fungoval, by byl:

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

Ve výše uvedeném názvu souborů s mezerami nebo jinými obtížnými znaky budou zpracovány správně.

Pokročilejší přístup by mohl použít pole bash:

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

V tomto případě FILES je řada názvů souborů. Rodiče obklopující definici z ní dělají pole. * je mimo uvozovky. Konstrukce "${FILES[@]}" je zvláštní případ: rozbalí se na seznam řetězců, kde každý řetězec je jedním z názvů souborů. Názvy souborů s mezerami nebo jinými obtížnými znaky budou zpracovány správně.

Komentáře

  • skvělé, že fungovalo
  • It ‚ stojí za zmínku, že pokud ‚ předáváte podobné cesty kolem funkcí, musíte zajistit, abyste proměnnou citovali samostatně spíše než zřetězení jako součást většího řetězce: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = přerušení

Odpověď

Přestože použití polí, jak ukazuje John1024, dává mnohem větší smysl, zde můžete také použít operátor split + glob (ponechání skalární proměnná bez uvozovek).

Protože chcete pouze globální část tohoto operátoru, musíte deaktivovat část 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 

Odpověď

Co můžete udělat, je ponechat mimo uvozovky pouze zástupné znaky.
Něco jako:
pro soubory „s mezerami“ * „. txt „
do
zpracování
hotovo
Pokud se zástupné znaky samy rozšíří do mezer, pak nebudete muset použít přístup soubor na řádek, jako použít ls -l ke generování seznamu souborů a k získání každého souboru použijte čtení bash.

Odpověď

Jednořádkové řešení (pro spuštění v Terminálu):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

pro váš případ / OP změňte "./" na "/home/john/my directory/"

Použití v souboru skriptu:

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

Výše uvedené funkce lze dosáhnout i tímto (doporučeným) způsobem:

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

Stručný / krátký POPIS:

"./": toto je aktuální adresář. zadejte cestu k adresáři.
-not -type d: zde jej -not konfiguruje tak, aby přeskočil další uvedený typ, & další uvedený -type je d = adresáře, takže bude přeskočen adresáře. K přeskočení souborů použijte f namísto d. Místo d použijte l k přeskočení souborů symbolických odkazů.
-maxdepth 1: konfiguruje jej tak, aby vyhledával soubory pouze na aktuální úrovni adresáře (aka: one). Chcete-li najít soubor uvnitř každé & první úrovně podadresáře, nastavte maxdepth na 2. Pokud se -maxdepth nepoužívá, bude hledat rekurzivně (uvnitř podadresářů) atd.
-iname "*.jpg": zde jej -iname konfiguruje tak, aby vyhledával soubory a ignoroval (horní / dolní) -případ v názvu souboru / příponě. -name neignoruje velká a malá písmena. -lname najde symbolické odkazy. atd.
-print0: vytiskne cestu aktuálního souboru na standardní výstup, následovaný znakem ASCII NUL (kód znaku 0), který později detekovat pomocí read v while.
IFS=: zde se používá v případě, že název souboru končí mezerou. K detekci každého nalezeného názvu souboru používáme NUL / "" / \0 s IFS. Protože “ najít “ je nakonfigurován tak, aby je oddělil od \0 který produkuje -print0.
read -r -d $"\0" fileName: the $"\0" is "" / NUL. -r byl použit v případě, že název souboru má zpětné lomítko.
číst [-ers] [-a aname] [-d oddělovač] [-i text] [-n nchars] [-N nchars] [-p výzva] [-t časový limit] [-u fd] [jméno. ..] […]
-r Zpětné lomítko nepůsobí jako úniková postava. Zpětné lomítko je považováno za část řádku. Zejména dvojici zpětného lomítka a nového řádku nelze použít jako pokračování řádku.
done < <(...): Zde se pro odesílání používá substituce procesu / výstup kanálu “ najít “ do “ přečíst “ of “ while “ -loop. Další informace: 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


V jiné odpovědi uživatele @ John1024 ukázal skvělé řešení založené na bash, které nepoužívá “ find „, externí nástroj.
“ find “ je velmi efektivní & rychlý, dávám přednost tomu, když je k dispozici příliš mnoho souborů.

V řešení @ John1024 to vytiskne řádek s odpovídajícím pravidlem, pokud v adresáři není žádný soubor, takže k přeskočení se používá řádek [ ! -e "${f}" ]...,
zde je jednořádkové řešení, které lze použít přímo v Terminálu:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Zde je 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;  

Poznámka: má-li adresář v DIR "/" lomítko (indikátor adresáře) na konci, pak v odpovídajícím pravidle , opět není použití "/" nutné,
Nebo postupujte opačně: v DIR nepoužívejte "/" na konci a tak jej použijte v odpovídajícím pravidle "$DIR"/*.txt


Tato zvláštní kontrola pomocí [ ! -e "${f}" ]... kódu se lze vyhnout, pokud pod shell-option (aka: “ shopt „) je použit nebo povolen:
shopt -s nullglob

Pokud byl skript změněn skriptem, pak v jiném skriptovém programu založeném na bash vytváří neočekávané / neočekávané problémy.

Chcete-li mít konzistentní chování ve všech skriptech, které používají stav bash, bash-shell-option by měl být zaznamenán / uložen ve vašem skriptu a jakmile ve skriptu provedete primární funkce, pak by měl být tento shell-option nastaven zpět na předchozí nastavení.

Používáme backtick (aka: grave-accent, aka: backquote) `...` nahrazení příkazů (pro interní kódy příkazů bash atd.), aby se neobjevil nový dílčí shell, zachovat doslovný význam zpětného lomítka, pro širší podporu (aka: přenositelnost) atd. Protože interní bash-příkazy založené na backticku atd. mohou být často prováděny ve stejném prostředí jako skript atd., A tak je o něco rychlejší = „c4609ee59c“>

lepší a také lepší pro účely, které zde zpracováváme. Pokud dáváte přednost$(...)nahrazení příkazů, použijte toto, kdokoli má svobodu & právo zvolit si, co dává přednost, vyhnout se atd. Další informace : zde .

Takže výše uvedený skript se znovu zobrazí, & tentokrát s předchozím nastavením obnoveného výnosu, před ukončením skriptu:

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

Výstup shopt -p shoptName (například: shopt -p dotglob) může být
buď toto, shopt -u shoptName (u je unset / disabled / off / 0)
nebo: shopt -s shoptName ( s je set / enabled / on / 1)
pozice písmene "s" nebo "u" je vždy na 7 (protože v bash a Pozice písmene řetězce začíná od 0, tj. první písmeno řetězce je na pozici 0)
My můžete získat "u" nebo a uložit jej do proměnné, abychom ji mohli použít k obnovení předchozího stavu.
A pokud použijeme tento (výše uvedený) způsob, jak uložit / obnovit shopt state, pak můžeme nepoužívejte externí nástroj "grep".

Chcete-li zobrazit soubor "txt", který začíná ".", tj. pro zobrazení skrytého "txt" souboru je třeba povolit "dotglob" shopt.

Tentokrát tedy "dotglob" je zahrnuto & povoleno zobrazovat SKRYTÉ "txt" soubory:

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

Existuje více jednoduchých způsobů uložení / obnovení možnost / hodnota.
Isaac zveřejnil zde , jak uložit + obnovit env / Shopt variable / option state / value.

Uložení stavu shopt “ nullglob „:

... # your primary-function codes/commands, etc lines
Obnovení předchozího předchozího stavu “ nullglob „, před ukončením skriptu:
eval "$p_nullglob" ;

Takto lze uložit více stavů:
p_multipleShopt="`shopt -p nullglob dotglob`";
a proces obnovení je stejný jako dříve:
eval "$p_multipleShopt" ;

Uložit VŠECHNY vyřazené stavy tímto způsobem:
p_allShopt="`shopt -p`";
a proces obnovení je stejný jako dříve:
eval "$p_allShopt" ;

Takže tady je další řešení založené na 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;  

Používání eval je bezpečné výše, protože proměnná "$p_allShopt" nedrží data poskytnutá uživatelem nebo data, která nejsou – sanitized, This var is holding output of bash internal command shopt.
Pokud se stále chcete vyhnout eval, použijte níže, takže 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;  

Několik (jiných) pozoruhodných & související OBCHOD , které mohou být užitečné, jsou:

  • nocaseglob: Pokud je nastaven, Bash odpovídá názvům souborů v Mód nerozlišuje velká a malá písmena při rozšiřování názvu souboru.
  • nocasematch: Je-li nastavena, Bash při porovnávání při provádění case nebo [[ podmíněné příkazy, když provádíte rozšiřování slov o nahrazení vzorů nebo filtrujete možná dokončení v rámci programovatelného dokončení.
  • dotglob: Je-li nastaven, Bash obsahuje názvy souborů začínající ‘.’ ve výsledcích rozšíření názvu souboru. Názvy souborů ‘.’ a ‘..’ se musí vždy explicitně shodovat, i když je nastaven dotglob.
  • nullglob: Pokud je nastaven , Bash umožňuje, aby se vzory názvů souborů, které neodpovídají žádným souborům, rozbalily na nulový řetězec, spíše než na sebe.
  • extglob: Je-li nastaven, budou funkce rozšířeného porovnávání vzorů popsány výše (viz Porovnávání vzorů ) jsou povoleny.
  • globstar: Je-li nastaven, vzor ‘**’ použitý v kontextu rozšíření názvu souboru bude odpovídat všem souborům a nula nebo více adresářů a podadresářů. Pokud za vzorem následuje ‘/’, shodují se pouze adresáře a podadresáře.

Odpovědět

Chcete-li zpracovat soubor souborů, zvažte, že u jejich názvu může být mezera nebo jiný stvolový kód, takže před zahájením procesu, například for loop nebo find command nastavte IFS bash env variable na:

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

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *