Jeg har problemer med at få det grundlæggende i Bash-scripting ned. Her er hvad jeg har hidtil:
#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done
Alt hvad jeg vil gøre er at liste alle .txt
filer i en for
-sløjfe, så jeg kan gøre ting med dem. Men mellemrummet i my directory
og stjernen i *.txt
bare spiller ikke pænt. Jeg forsøgte at bruge det med og uden dobbelt anførselstegn, med og uden krøllede seler på variabelnavne og kan stadig ikke “udskrive alle .txt
filer.
Dette er en meget grundlæggende ting, men jeg kæmper stadig, fordi jeg er træt og ikke kan tænke lige.
Hvad laver jeg forkert?
Jeg har været i stand til at anvende med succes ovenstående script, hvis mine FILER ikke har et mellemrum eller en stjerne … Jeg var nødt til at eksperimentere med eller uden brug af dobbelt anførselstegn og seler for at få det til at fungere. Men i det øjeblik, jeg har begge mellemrum og en stjerne, ødelægger det alt.
Svar
Inde i citater, *
udvides ikke til en liste over filer. For at bruge et sådant jokertegn med succes, skal det være uden for anførselstegn.
Selvom jokertegnet blev udvidet, ville udtrykket "${FILES}"
resultere i en enkelt streng, ikke en liste over filer.
En tilgang, der ville fungere, ville være:
#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done
I ovenstående filnavne med mellemrum eller andre vanskelige tegn håndteres korrekt.
En mere avanceret tilgang kan bruge bash-arrays:
#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done
I dette tilfælde FILES
er en matrix med filnavne. Forældrene omkring definitionen gør det til en matrix. Bemærk, at *
er uden for anførselstegn. Konstruktionen "${FILES[@]}"
er et specielt tilfælde: det udvides til en liste over strenge, hvor hver streng er et af filnavne. Filnavne med mellemrum eller andre vanskelige tegn håndteres korrekt.
Kommentarer
Svar
Mens brug af arrays som vist af John1024 giver meget mere mening, her kan du også bruge split + glob-operatoren (forlader en skalarvariabel, der ikke er citeret).
Da du kun vil have glob-delen af denne operatør, skal 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
Hvad du kan gøre er at kun lade jokertegnet være uden for anførselstegn.
Noget som:
for a i “filer med mellemrum” * “. txt “
gør
behandling
færdig
Hvis jokertegnene selv udvides til mellemrum, skal du ikke bruge en fil pr. linjemetode, ligesom brug ls -l til at generere listen over filer og brug bash-læsning for at få hver fil.
Svar
Enkelt linjebaseret løsning, (køres 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;
til din / OPs sag, skift "./"
til "/home/john/my directory/"
Bruges i en scriptfil:
#!/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;
Ovenstående funktionalitet kan også opnås på denne (anbefales) måde:
#!/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 aktuelle mappe. angiv en biblioteksti.
• -not -type d
: her konfigurerer -not
den for at springe næste nævnte type, & næste nævnte -type
er d
= mapper, så den springer mapper. Brug f
i stedet for d
til at springe filer over. Brug l
i stedet for d
til at springe symlink-filer over.
• -maxdepth 1
: konfigurerer den til kun at finde filer inden for det aktuelle (aka: one) katalogniveau. For at finde filen inden for hvert & første underkatalogniveau skal du indstille maxdepth til 2. Hvis -maxdepth ikke bruges, søger den rekursivt (inde i underkataloger) osv.
• -iname "*.jpg"
: her konfigurerer -iname
det til at finde filer og ignorere (øverste / nederste) -æske i filnavn / udvidelse. -name
ignorerer ikke store og små bogstaver. -lname
finder symlinks. etc.
• -print0
: det udskriver stienavn på den aktuelle fil til standardoutput efterfulgt af et ASCII NUL-tegn (tegnkode 0), som vi vil senere registrere ved hjælp af read
i while
.
• IFS=
: her bruges det, hvis et filnavn slutter med et mellemrum. Vi bruger NUL / ""
/ \0
med IFS til at opdage hvert fundet filnavn. Som ” find ” er konfigureret til at adskille dem med \0
som er produceret af -print0
.
• read -r -d $"\0" fileName
: $"\0"
er ""
/ NUL. -r
blev brugt, hvis et filnavn har et tilbageslag.
• læs [-ers] [-a aname] [-d afgrænsning] [-i tekst] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [navn. ..] […]
-r Backslash fungerer ikke som et escape-tegn. Backslash betragtes som en del af linjen. Især kan et backslash-newline-par ikke bruges som en linjefortsættelse.
• done < <(...)
: Proces-erstatning bruges her til at sende / rørudgang fra ” find ” i ” læs ” af ” mens ” -loop. Flere oplysninger: 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 andet svar fra @ John1024 har han vist en fantastisk bash-baseret løsning, der ikke bruger ” find “, et eksternt hjælpeprogram.
” find ” er meget effektiv & hurtigt, jeg foretrækker det, når der er for mange filer til at håndtere.
i @ John1024s løsning vil den udskrive matching-regel linje, når der ikke er nogen fil i biblioteket, så under [ ! -e "${f}" ]...
bruges linjen til at springe over,
her er en-linjeløsning, der skal bruges 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 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;
Bemærk: Hvis biblioteket i DIR har "/"
skråstreg (biblioteksindikator) i slutningen, så i matching-regel , igen ved hjælp af "/"
er ikke nødvendigt,
Eller gør det modsatte: i DIR skal du ikke bruge "/"
i slutningen og brug det så i matching-rule "$DIR"/*.txt
Den ekstra kontrol ved hjælp af [ ! -e "${f}" ]...
kode kan undgås, hvis under shell-option (aka: ” shopt “) bruges eller aktiveret:
shopt -s nullglob
Hvis en shopt-tilstand blev ændret af et script, skaber det i andre bash-baserede script-programmer uventede / uventede problemer.
At have ensartet adfærd på tværs af alle scripts, der bruger tilstanden bash, bash-shell-option skal registreres / gemmes inde i dit script, og når du er færdig med at bruge dine primære funktioner i dit script, skal shell-optionen sættes tilbage til tidligere indstillinger.
Vi bruger backtick (aka: grave-accent, aka: backquote) `...`
kommandosubstitution (til interne bash-kommandokoder osv.) for ikke at gyde en ny sub-shell, bevarer bogstavelig betydning af tilbageslag, for bredere support (aka: portabilitet) osv. Som backtick-baserede interne bash-kommandoer osv. kan ofte udføres i samme shell som scriptet osv. Og så er det lidt hurtigere & bedre og også bedre til det formål, vi håndterer her. Hvis du foretrækker $(...)
kommandosubstitution, skal du bruge det, enhver har frihed & ret til at vælge hvad de foretrækker, undgå osv. Mere info : her .
Så ovenstående script vises igen, & denne gang med tidligere indstillinger for en shopt gendannet, inden scriptet afsluttes:
#!/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 af shopt -p shoptName
(for eksempel: shopt -p dotglob
) kan være
enten dette: shopt -u shoptName
(u
er unset
/ deaktiveret / slukket / 0
)
eller dette: shopt -s shoptName
( s
er set
/ aktiveret / til / 1
)
Positionen af bogstavet "s"
eller "u"
er altid ved 7
(fordi i bash er en streng “s bogstavposition begynder fra 0
, det vil sige, det første bogstav i en streng er ved position 0
)
Vi kan få dette "u"
eller og gemme den i en variabel, så vi kan bruge den til at gendanne tidligere tilstand.
Og hvis vi anvender denne (nævnt ovenfor) måde at gemme / gendanne shopt-tilstand, så kan vi undgå at bruge eksternt værktøj "grep"
.
For at se "txt"
fil, der begynder med "."
, dvs. for at se skjult "txt"
-fil, er vi nødt til at aktivere "dotglob"
shopt.
Så denne gang nedenfor er "dotglob"
inkluderet & aktiveret til at 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;
Der er mere enkel måde at gemme / gendanne shopt på option / værdi.
Isaac postede her , hvordan man gemmer + gendanner Env / Shopt-variabel / optionstilstand / værdi.
Gemmer shopt-tilstand for ” nullglob “:
... # your primary-function codes/commands, etc lines
Gendannelse af tidligere shoptilstand for ” nullglob “, før scriptet afsluttes:eval "$p_nullglob" ;
Flere shopt-tilstande kan gemmes på denne måde:
p_multipleShopt="`shopt -p nullglob dotglob`";
og gendannelsesprocessen er den samme som før:
eval "$p_multipleShopt" ;
Gem ALLE shopt-stater på denne måde:
p_allShopt="`shopt -p`";
og gendannelsesprocessen er den samme som før:
eval "$p_allShopt" ;
Så her er en anden bash-baseret 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;
Brug af eval
er sikkert i ovenstående, da variablen "$p_allShopt"
ikke indeholder data leveret af en bruger eller data, der ikke er desinficeret, at var holder output af bash intern kommando shopt
.
Hvis du stadig vil undgå eval
skal du bruge nedenstående 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å (andre) bemærkelsesværdige & relateret SHOPT , der kan være nyttige, er:
- nocaseglob: Hvis indstillet, matcher Bash filnavne i en store og små bogstaver, når der udføres filnavneudvidelse.
- nocasematch: Hvis indstillet, matcher Bash mønstre på en sagfølsom måde, når der udføres matching, mens
case
eller[[
betingede kommandoer, når du udfører ordudvidelser med mønstersubstitution, eller når du filtrerer mulige færdiggørelser som en del af den programmerbare færdiggørelse. - dotglob: Hvis angivet, inkluderer Bash filnavne, der begynder med en
‘.’
i resultaterne af filnavnudvidelse. Filnavnene‘.’
og‘..’
skal altid matches eksplicit, selvom dotglob er indstillet. - nullglob: Hvis indstillet , Bash tillader filnavnmønstre, der ikke matcher nogen filer, at udvide til en nullstreng snarere end sig selv.
- extglob: Hvis angivet, er de udvidede mønstermatchfunktioner beskrevet ovenfor (se Mønstertilpasning ) er aktiveret.
- globstar: Hvis angivet, vil mønsteret
‘**’
, der bruges i en filudvidelseskontekst, matche alle filer og nul eller flere kataloger og underkataloger. Hvis mønsteret følges af en‘/’
, er det kun mapper og underkataloger, der matcher.
Svar
Når du vil behandle på et sæt filer, overvejer du, at deres navn kan være mellemrum eller anden scape-kode vil være der, så inden du starter din proces, såsom for loop
eller find command
indstil IFS bash env variable
til:
IFS=$(echo -en "\n\b")
for f in "$DIR"/*.txt
= finfor f in "$DIR/*.txt"
= pauser