Bash-scriptfout met strings met paden die spaties en jokertekens bevatten

Ik heb problemen om de basisprincipes van Bash-scripting onder de knie te krijgen. Dit is wat ik tot nu toe heb:

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

Het enige dat ik wil doen is een lijst maken van alle .txt bestanden in een for -lus zodat ik er dingen mee kan doen. Maar de spatie in de my directory en het sterretje in *.txt spelen gewoon niet goed. Ik heb geprobeerd het te gebruiken met en zonder dubbele aanhalingstekens, met en zonder accolades op variabelenamen en kan nog steeds “niet alle .txt bestanden afdrukken.

Dit is een heel basaal, maar ik heb nog steeds moeite omdat ik moe ben en niet helder kan denken.

Wat doe ik verkeerd?

Ik heb met succes kunnen solliciteren het bovenstaande script als mijn BESTANDEN geen spatie of asterisk hebben … Ik moest experimenteren met of zonder het gebruik van dubbele aanhalingstekens en accolades om het te laten werken. Maar op het moment dat ik zowel spaties als een asterisk heb, verpest het alles.

Answer

Tussen aanhalingstekens, de * wordt niet uitgebreid naar een lijst met bestanden. Om een dergelijk jokerteken met succes te gebruiken, moet het buiten aanhalingstekens staan.

Zelfs als het jokerteken zou worden uitgebreid, zou de uitdrukking "${FILES}" resulteren in een enkele string, niet een lijst met bestanden.

Een benadering die zou werken zou zijn:

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

In het bovenstaande, bestandsnamen met spaties of andere moeilijke tekens worden correct afgehandeld.

Een meer geavanceerde benadering zou bash-arrays kunnen gebruiken:

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

In dit geval FILES is een reeks bestandsnamen. De parens rond de definitie maken er een array van. Merk op dat de * buiten de aanhalingstekens staat. Het construct "${FILES[@]}" is een speciaal geval: het wordt uitgebreid naar een lijst met strings waarbij elke string een van de bestandsnamen is. Bestandsnamen met spaties of andere moeilijke karakters worden correct behandeld.

Reacties

  • geweldig dat werkte
  • Het ‘ is het vermelden waard dat als u ‘ paden als deze door functies leidt, u ervoor moet zorgen dat u de variabele alleen citeert in plaats van het samen te voegen als onderdeel van een grotere tekenreeks: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = breaks

Answer

Hoewel het veel logischer is om arrays zoals getoond door John1024 te gebruiken, kun je hier ook de split + glob operator gebruiken (laat een scalaire variabele zonder aanhalingstekens).

Aangezien u alleen het glob-gedeelte van die operator wilt, moet u het split -gedeelte uitschakelen:

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

Answer

Wat u kunt doen is alleen de jokertekens buiten aanhalingstekens laten.
Iets als:
voor a in “bestanden met spaties” * “. txt “
doen
verwerken
klaar
Als de jokertekens zelf uitbreiden naar spaties, dan heb je geen” bestand per regel “-benadering nodig, zoals gebruik ls -l om de lijst met bestanden te genereren en gebruik bash read om elk bestand op te halen.

Answer

Op één regel gebaseerde oplossing (om in Terminal te draaien):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

voor uw / OP “s geval, verander de "./" in "/home/john/my directory/"

Te gebruiken in een scriptbestand:

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

Bovenstaande functionaliteit kan ook op deze (aanbevolen) manier worden bereikt:

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

Beknopte / korte BESCHRIJVING:

"./": dit is de huidige directory. specificeer een directorypad.
-not -type d: hier configureert de -not het om de volgende genoemde type, & volgende genoemde -type is d = mappen, dus het zal overslaan mappen. Gebruik f in plaats van d om bestanden over te slaan. Gebruik l in plaats van d om symlink-bestanden over te slaan.
-maxdepth 1: configureert het om alleen bestanden binnen het huidige (ook bekend als: één) directoryniveau te vinden. Om een bestand in elk & eerste submapniveau te vinden, stelt u maxdepth in op 2. Als -maxdepth niet wordt gebruikt, zal het recursief zoeken (in submappen), enz.
-iname "*.jpg": hier configureert de -iname het om bestanden te zoeken en te negeren (boven / onder) -case in bestandsnaam / extensie. De -name negeert hoofdletters niet. De -lname vindt symlinks. enz.
-print0: het drukt de padnaam van het huidige bestand af naar de standaarduitvoer, gevolgd door een ASCII NUL-teken (tekencode 0), wat we zullen later detecteren met behulp van de read in while.
IFS=: hier wordt het gebruikt voor het geval een bestandsnaam eindigt op een spatie. We gebruiken NUL / "" / \0 met IFS om elke gevonden bestandsnaam te detecteren. Zoals ” find is ” geconfigureerd om ze te scheiden met \0 die wordt geproduceerd door -print0.
read -r -d $"\0" fileName: de $"\0" is "" / NUL. De -r werd gebruikt in het geval een bestandsnaam een backslash heeft.
lees [-ers] [-a aname] [-d scheiding] [-i tekst] [-n nchars] [-N nchars] [-p prompt] [-t time-out] [-u fd] [naam. ..] […]
-r Backslash werkt niet als een ontsnappingskarakter. De backslash wordt beschouwd als onderdeel van de regel. In het bijzonder mag een backslash-nieuwe regel paar niet worden gebruikt als een regel voortzetting.
done < <(...): Proces-vervanging die hier wordt gebruikt om / pipe-uitvoer van ” vind ” naar de ” lees ” van ” terwijl ” -loop. Meer informatie: 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


In een ander antwoord van @ John1024 heeft hij een geweldige bash-gebaseerde oplossing getoond, die niet de vind “, een extern hulpprogramma.
” vind ” is zeer effectief & snel, ik geef er de voorkeur aan als er te veel bestanden zijn om te verwerken.

in @ John1024 “s oplossing zal het de regel met overeenkomende regel als er geen bestand in de directory staat, dus hieronder wordt de regel [ ! -e "${f}" ]... gebruikt om die over te slaan,
hier is een oplossing met één regel om rechtstreeks in Terminal te gebruiken:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Hier is een 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;  

Opmerking: als de directory in DIR een "/" slash (directory-indicator) aan het einde heeft, dan in de overeenkomende regel , opnieuw het gebruik van de "/" is niet nodig,
Of doe het tegenovergestelde: gebruik in DIR niet de "/" aan het einde en dus gebruik het in de matching-regel "$DIR"/*.txt


Die extra controle door de [ ! -e "${f}" ]... code kan worden vermeden, indien lager dan shell-optie (ook bekend als: ” shopt “) wordt gebruikt of ingeschakeld:
shopt -s nullglob

Als een shopt-status werd gewijzigd door een script, dan creëert het in een ander op bash gebaseerd scriptprogramma onverwachte / onverwachte problemen.

Om consistent gedrag te hebben voor alle scripts die bash, de status van de bash-shell-optie moet worden opgenomen / opgeslagen in je script, en als je eenmaal klaar bent met het gebruiken van je primaire functies in je script, dan moet die shell-optie teruggezet worden naar de vorige instellingen.

We gebruiken backtick (ook bekend als: grave-accent, oftewel: backquote) `...` opdrachtsubstitutie (voor interne bash-opdrachtcodes, enz.), om geen nieuwe subshell te spawnen, behoud de letterlijke betekenis van backslash, voor bredere ondersteuning (ook bekend als: portabiliteit), etc, Omdat backtick gebaseerde interne bash-commandos, etc vaak in dezelfde shell kunnen worden uitgevoerd als het script, etc, en dus is het een beetje sneller & beter, en ook beter voor het doel dat we hier behandelen. Als je de voorkeur geeft aan $(...) commando-vervanging, gebruik dat dan, iedereen heeft de vrijheid & recht om te kiezen wat ze verkiezen, vermijden, enz. Meer informatie : hier .

Dus het bovenstaande script wordt opnieuw getoond, & dit keer met eerdere instellingen van een shopt hersteld, voordat het script wordt beëindigd:

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

Uitvoer van shopt -p shoptName (bijvoorbeeld: shopt -p dotglob) kan
ofwel zijn: shopt -u shoptName (de u is unset / uitgeschakeld / uit / 0)
of, dit: shopt -s shoptName ( de s is set / enabled / on / 1)
De positie van de letter "s" of "u" is altijd 7 (omdat, in bash, een tekenreeks “s letterpositie begint vanaf 0, dat wil zeggen, de eerste letter van een tekenreeks bevindt zich op positie 0)
We kan deze "u" of en sla het op in een variabele, zodat we het kunnen gebruiken om de vorige staat te herstellen.
En als we deze (hierboven genoemde) manier toepassen om de shopt-status op te slaan / te herstellen, dan kunnen we gebruik geen externe tool "grep".

Om het "txt" -bestand te bekijken dat begint met ".", dat wil zeggen, om het verborgen "txt" -bestand te bekijken, moeten we "dotglob" shopt inschakelen.

Dus deze keer hieronder is "dotglob" opgenomen & ingeschakeld om HIDDEN "txt" bestanden weer te geven:

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

Er zijn eenvoudigere manieren om shopt op te slaan / te herstellen optie / waarde.
Isaac plaatste hier , hoe Env op te slaan en te herstellen / Shopt variabele / optie staat / waarde.

Shopt staat van ” nullglob ” opslaan:

... # your primary-function codes/commands, etc lines
Terugzetten van de vorige shopt-status van ” nullglob “, voordat het script wordt afgesloten:
eval "$p_nullglob" ;

Meerdere shopt-statussen kunnen op deze manier worden opgeslagen:
p_multipleShopt="`shopt -p nullglob dotglob`";
en het herstelproces is hetzelfde als voorheen:
eval "$p_multipleShopt" ;

Sla ALLE shopt-toestanden op deze manier op:
p_allShopt="`shopt -p`";
en het herstelproces is hetzelfde als voorheen:
eval "$p_allShopt" ;

Dus hier is nog een bash-gebaseerde oplossing:

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

Het gebruik van eval is hierboven veilig, aangezien de variabele "$p_allShopt" geen gegevens bevat die door een gebruiker zijn verstrekt of gegevens die niet- sanitized, die var houdt de uitvoer vast van het interne bash-commando shopt.
Als je nog steeds eval wilt vermijden, gebruik dan hieronder 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;  

Weinig (andere) opmerkelijke & gerelateerde SHOPT die nuttig kunnen zijn, zijn:

  • nocaseglob: indien ingesteld, komt Bash overeen met bestandsnamen in een niet hoofdlettergevoelig bij het uitvoeren van bestandsnaamuitbreiding.
  • nocasematch: indien ingesteld, komt Bash overeen met patronen op een hoofdletterongevoelige manier bij het uitvoeren van matching tijdens het uitvoeren van case of [[ voorwaardelijke commandos, bij het uitvoeren van woorduitbreidingen voor patroonvervanging of bij het filteren van mogelijke aanvullingen als onderdeel van programmeerbare voltooiing.
  • dotglob: indien ingesteld, bevat Bash bestandsnamen die beginnen met een ‘.’ in de resultaten van bestandsnaamuitbreiding. De bestandsnamen ‘.’ en ‘..’ moeten altijd expliciet overeenkomen, zelfs als dotglob is ingesteld.
  • nullglob: indien ingesteld , Bash staat bestandsnaampatronen die niet overeenkomen met bestanden toe om uit te breiden naar een null-string, in plaats van zichzelf.
  • extglob: indien ingesteld, zullen de uitgebreide patroonvergelijkingsfuncties hierboven beschreven (zie Patroonovereenkomst ) zijn ingeschakeld.
  • globstar: indien ingesteld, zal het patroon ‘**’ dat wordt gebruikt in een bestandsnaamuitbreidingscontext overeenkomen met alle bestanden en nul of meer mappen en submappen. Als het patroon wordt gevolgd door een ‘/’, komen alleen mappen en submappen overeen.

Antwoord

Als u een set bestanden wilt verwerken, bedenk dan dat er bij hun naam ruimte of andere scape-code zal zijn, dus voordat u begint met uw proces, zoals for loop of find command stel de IFS bash env variable in op:

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

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *