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
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")
for f in "$DIR"/*.txt
= finefor f in "$DIR/*.txt"
= přerušení