Megjegyzések
- Nem értek egyet azzal, hogy ez duplikátum lenne. Az elfogadott válasz megválaszolja, hogyan lehet hurkolni a fájlneveket szóközökkel; ennek semmi köze " miért hurokolja át a keresés ' kimenet rossz gyakorlatát ". Azért találtam ezt a kérdést (a másikat nem), mert a fájlneveket szóközökkel kell hurcolnom, mint például: for file for $ LIST_OF_FILES; do … ahol a $ LIST_OF_FILES nem a keresés eredménye; ' csak a fájlnevek listája (új sorokkal elválasztva).
- @CarloWood – a fájlnevek tartalmazhatnak új sorokat, így a kérdésed meglehetősen egyedi: átkapcsolás a fájlnevek listája, amelyek tartalmazhatnak szóközt, de nem tartalmazhatnak új sort. Azt hiszem, ' az IFS technikát kell használnia annak jelzésére, hogy a törés ' \ n '
- @ Diagonwoah, soha nem vettem észre, hogy a fájlnevek tartalmazhatnak új sorokat. Leginkább (csak) linuxot / UNIX-ot használok, és még a szóközök is ritkák; Egész életemben soha nem láttam új sorok használatát: p. Akár meg is tilthatják ezt az imhot.
- @CarloWood – A fájlnevek nullával végződnek (' \ 0 ' , ugyanaz, mint ' '). Minden más elfogadható.
- @CarloWood Ne feledje, hogy az emberek elsőként szavaznak, másodikként olvasnak …
Válasz
Rövid válasz (legközelebb a válaszához, de kezeli a szóközöket)
OIFS="$IFS" IFS=$"\n" for file in `find . -type f -name "*.csv"` do echo "file = $file" diff "$file" "/some/other/path/$file" read line done IFS="$OIFS"
Jobb válasz (a helyettesítő karaktereket és az új sorokat a fájlnevekben is kezeli)
find . -type f -name "*.csv" -print0 | while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty done
Legjobb válasz ( Gilles alapján ” válasz )
find . -type f -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty " exec-sh {} ";"
Vagy még jobb, hogy elkerülje az egyik futtatását sh
fájlonként:
find . -type f -name "*.csv" -exec sh -c " for file do echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty done " exec-sh {} +
Hosszú válasz
Három problémád van:
- Alapértelmezés szerint a héj felosztja a parancs kimenetét szóközökön, füleken és új sorokban
- A fájlnevek helyettesítő karaktereket tartalmazhatnak, amelyek kibővülne
- Mi van, ha van egy könyvtár, amelynek neve
*.csv
végződésű?
1. Csak az új vonalakon történő felosztás
Ahhoz, hogy kitaláljuk, mire állítsuk a file
elemet, a shellnek kell kimenetet tennie a find
és értelmezze valahogy, különben a file
csak a find
teljes kimenete lenne .
A héj beolvassa a IFS
változót, amely alapértelmezés szerint <space><tab><newline>
értékre van állítva.
Ezután megnézi a find
kimenetének minden karakterét. Amint meglát minden olyan karaktert, amely “s a IFS
-ban van, úgy gondolja, hogy ez jelöli a fájlnév végét, ezért beállítja a file
bármely karakterre, amelyet eddig látott, és lefuttatja a ciklust. Ezután ott kezdődik, ahol abbahagyta, hogy megszerezze a következő fájlnevet, és futtatja a következő ciklust stb., amíg el nem éri a kimenet végét.
Tehát ezt ténylegesen megteszi:
for file in "zquery" "-" "abc" ...
Ha azt akarja mondani, hogy csak új vonalakon ossza fel a bemenetet, meg kell tennie
IFS=$"\n"
a for ... find
parancs előtt.
Ez a IFS
értéket a egyetlen új sor, így csak az új vonalakon oszlik meg, a szóközök és a tabulátorok sem.
Ha sh
vagy dash
ksh93
, bash
vagy zsh
helyett IFS=$"\n"
így inkább:
IFS=" "
Ez valószínűleg elég a szkript működéséhez, de ha érdekel néhány más sarok eset megfelelő kezelése, olvassa el a következőt …
2. $file
bővítése helyettesítő karakterek nélkül
A hurok belsejében, ahol te “>
a shell megpróbálja kibővíteni $file
(újra!).
Tartalmazhat szóközt, de mivel már beállítottuk a IFS
fenti, ez itt nem jelent problémát.
De tartalmazhat helyettesítő karaktereket is, például *
vagy ?
, ami kiszámíthatatlan viselkedéshez vezet. (Köszönet Gilles-nek, hogy rámutatott erre.)
Ha azt akarja mondani a héjról, hogy ne bővítse a helyettesítő karaktereket, tegye a változót idézőjelek közé, pl.
diff "$file" "/some/other/path/$file"
Ugyanez a probléma csíphet minket a
for file in `find . -name "*.csv"`
Például, ha ez a három fájl megvan
file1.csv file2.csv *.csv
(nagyon valószínűtlen, de mégis lehetséges)
Olyan lenne, mintha futottál volna
for file in file1.csv file2.csv *.csv
amely kibővül
for file in file1.csv file2.csv *.csv file1.csv file2.csv
file1.csv
és file2.csv
kétszer feldolgozandó.
Ehelyett meg kell tennünk
find . -name "*.csv" -print | while IFS= read -r file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty done
read
beolvassa a sorokat a standard bemenetről, szavakat bont a szavakra a IFS
szerint, és eltárolja azokat az Ön által megadott változónevekben.
Itt újra elmondjuk ne ossza szét a sort szavakra, és tárolja a sort a $file
mappában.
Vegye figyelembe azt is, hogy read line </dev/tty
-re változott.
Ez azért van, mert a hurokban belül a standard bemenet a find
a folyamaton keresztül.
Ha csak read
tennénk, akkor az egy fájlnév egy részét vagy egészét emésztené, és néhány fájl kihagyásra kerülne .
/dev/tty
az a terminál, ahonnan a felhasználó a parancsfájlt futtatja. Ne feledje, hogy ez hibát okoz, ha a szkriptet cronon keresztül futtatják, de feltételezem, hogy ez ebben az esetben nem fontos.
Akkor mi van, ha egy fájlnév új sorokat tartalmaz?
Ezt úgy kezelhetjük, hogy a -print
-et -print0
-re változtatjuk, és a pipeline:
find . -name "*.csv" -print0 | while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read char </dev/tty done
Ez arra készteti a find
-t, hogy minden fájlnév végére null bájt kerüljön. Az egyetlen bájt az egyetlen karakter, amely nem engedélyezett a fájlnevekben, ezért ennek minden lehetséges fájlnevet kezelnie kell, bármennyire is furcsa.
A fájlnév másik oldalra jutásához használjuk a IFS= read -r -d ""
.
Ahol a fenti read
-t használtuk, az newline alapértelmezett vonalválasztóját használtuk, de most a A “>
a nullat használja vonalválasztóként. A bash
mezőben nem adhat át NUL karaktert egy argumentumban egy parancsnak (még beépítettnek is), de a bash
megérti a -d ""
jelentése NUL elválasztva . Tehát a -d ""
felhasználásával read
ugyanazt a vonalhatárolót használja, mint a find
. Ne feledje, hogy a -d $"\0"
egyébként szintén működik, mert a bash
nem támogatja a NUL bájtokat üres karaktersorozatként kezeli.
Hogy helyes legyünk, hozzáadjuk a -r
-t is, amely szerint ne kezelje a visszavágásokat fájlnevek speciálisan. Például a -r
nélkül \<newline>
eltávolításra kerül, és a \n
átalakítás n
.
Ennek hordozhatóbb írási módja, amelyhez nincs szükség bash
vagy zsh
vagy emlékeztet a null bájtokra vonatkozó összes fenti szabályra (ismét Gillesnek köszönhetően):
find . -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read char </dev/tty " exec-sh {} ";"
* 3. Olyan könyvtárak kihagyása, amelyeknek A nevek végződése .csv
find . -name "*.csv"
megegyezik a something.csv
.
Ennek elkerülése érdekében adja hozzá a -type f
elemet a find
parancshoz.
find . -type f -name "*.csv" -exec sh -c " file="$0" echo "$file" diff "$file" "/some/other/path/$file" read line </dev/tty " exec-sh {} ";"
Ahogy glenn jackman rámutat, mindkét példában az egyes fájlok végrehajtására szolgáló parancsok alhéjban fut, tehát ha a cikluson belül megváltoztat bármelyik változót, akkor azok elfelejtődnek.
Ha változókat kell beállítania, és továbbra is be kell állítania őket a ciklus végén átírhatja úgy, hogy így használja a folyamat helyettesítését:
i=0 while IFS= read -r -d "" file; do echo "file = $file" diff "$file" "/some/other/path/$file" read line </dev/tty i=$((i+1)) done < <(find . -type f -name "*.csv" -print0) echo "$i files processed"
Ne feledje, hogy ha ezt megpróbálja másolni és beilleszteni a parancssorba , read line
el fogja fogyasztani a echo "$i files processed"
-t, így a parancs nem fog futni.
Ennek elkerülése érdekében eltávolíthatja a következőt: read line </dev/tty
és elküldheti az eredményt egy olyan személyhívóra, mint például less
.
MEGJEGYZÉSEK
Eltávolítottam a pontosvesszőket (;
) a hurok. Ha akarja, visszahelyezheti őket, de nincs rájuk szükség.
Manapság a $(command)
gyakoribb, mint a `command`
. Ez főleg azért van, mert könnyebb írni $(command1 $(command2))
, mint `command1 \`command2\``
.
read char
nem igazán olvas karaktert.Egy egész sort olvas, ezért megváltoztattam a következőre: read line
.
Megjegyzések
-
while
egy folyamatban problémákat okozhat a létrehozott alhéjban (a ciklusblokkban található változók nem láthatók például a parancs befejezése után). A bash használatával a bemenet átirányítását és a folyamat helyettesítését használnám:while read -r -d $'\0' file; do ...; done < <(find ... -print0)
- Persze, vagy egy heredocot:
while read; do; done <<EOF "$(find)" EOF
. Nem olyan könnyen olvasható. - @glenn jackman: Éppen most próbáltam további magyarázatokat adni. Csak jobbá vagy rosszabbá tettem?
- Nem kell ' nem kell
IFS, -print0, while
ésread
ha a (z)find
elemet teljes egészében kezeli, ahogy az alábbi megoldásom mutatja. - Az első megoldás minden karakterrel megbirkózik, kivéve az új sort ha a
set -f
gombbal is kikapcsolja.
Válasz
Ez a szkript meghiúsul, ha bármely fájlnév tartalmaz szóközöket vagy héjba simuló karaktereket \[?*
. A find
parancs soronként egy fájlnevet ad ki. Ezután a `find …`
parancs behelyettesítését a shell a következőképpen értékeli:
- hajtsa végre a
find
parancsot, ragadja meg a kimenetét. - A
find
kimenetet ossza szét külön szavakra. Bármely szóköz karakter szóelválasztó. - Ha minden szó hibás minta, akkor bontsa ki az egyező fájlok listájára.
Például: tegyük fel, hogy az aktuális könyvtárban három fájl van, ezek neve: `foo* bar.csv
, foo 1.txt
és foo 2.txt
.
- A
find
parancs visszaadja a./foo* bar.csv
parancsot. - A héj felosztja ezt a karakterláncot a szóköznél, két szót előállítva:
./foo*
ésbar.csv
. - Mivel
./foo*
egy fénylő metakaraktert tartalmaz, kibővül a megfelelő fájlok listájával:./foo 1.txt
és./foo 2.txt
. - Ezért a
for
ciklust egymás után hajtják végre./foo 1.txt
,./foo 2.txt
ésbar.csv
.
A legtöbb problémát ebben a szakaszban elkerülheti, ha tompítja a szófelosztás és a fordulat huncutkodni. A szófelosztás tompításához állítsa a IFS
változót egyetlen újsoros karakterre; így a find
kimenete csak új vonalaknál lesz felosztva, és szóközök maradnak. A hamisítás kikapcsolásához futtassa a set -f
futtatást. Ekkor a kódnak ez a része addig fog működni, amíg egyetlen fájlnév sem tartalmaz új sor karaktert.
IFS=" " set -f for file in $(find . -name "*.csv"); do …
(Ez nem része a problémának, de én javasoljuk a $(…)
használatát a `…`
fölött. Ugyanaz a jelentésük, de a háttér idézet verziónak furcsa idézési szabályai vannak.)
Az alábbiakban van egy másik probléma: diff $file /some/other/path/$file
legyen
diff "$file" "/some/other/path/$file"
Ellenkező esetben a $file
szavakra van osztva, és a szavakat globális mintákként kezeljük, mint a fenti substitutio paranccsal. Ha emlékeznie kell egy dologra a shell programozással kapcsolatban, ne feledje ezt: mindig használjon dupla idézőjeleket a változó bővítések ($foo
) és a parancshelyettesítések ( $(bar)
) , hacsak nem tudja, hogy fel akar osztani. (Fentről tudtuk, hogy a find
kimenetet sorokra szeretnénk osztani.)
Megbízható módszer a find
azt mondja neki, hogy futtasson egy parancsot minden megtalált fájlhoz:
find . -name "*.csv" -exec sh -c " echo "$0" diff "$0" "/some/other/path/$0" " {} ";"
Ebben az esetben egy másik megközelítés a két könyvtár összehasonlítása, bár meg kell kifejezetten kizárja az összes „unalmas” fájlt.
diff -r -x "*.txt" -x "*.ods" -x "*.pdf" … . /some/other/path
Megjegyzések
- I ' elfelejtette a helyettesítő karaktereket, mint a megfelelő idézet másik okát. Köszönöm! 🙂
-
find -exec sh -c 'cmd 1; cmd 2' ";"
helyett használd azfind -exec cmd 1 {} ";" -exec cmd 2 {} ";"
parancsot, mert a shellnek el kell takarnia a paramétereket, de nem találja ' t. Az itt található különleges esetben az echo " $ 0 " nem kell ' a szkript része, csak csatolja a -print elemet az';'
után. Nem vettél fel ' kérdést a folytatáshoz, de még ezt is megtalálással lehet megtenni, amint az alább látható a lelkemben. 😉 - @userunknown: A (z)
{}
paraméter használatának paraméterek részstruktúrájaként azfind -exec
fájlban nem hordozható, hogy ' miért van szükség a héjra.Nem értem ', hogy mit értesz azon, hogy „a shellnek el kell takarnia a paramétereket”; ha ' az idézés kérdése, akkor a megoldásom megfelelően van idézve. ' igaza van abban, hogy aecho
részt helyette-print
is elvégezheti. Az-okdir
egy meglehetősen friss GNU kereső kiterjesztés, amely ' nem mindenhol elérhető. Nem foglaltam bele ' a folytatásra várást, mert úgy gondolom, hogy a rendkívül gyenge felhasználói felület és a kérdező könnyen beillesztheti aread
parancsot a shell-kódrészletbe, ha akarja. - Az idézés a maszkolás egyik formája, nem ' ez? Nem értem ' megjegyzését azzal kapcsolatban, hogy mi hordozható és mi nem. A példád (alulról 2.) az -exec segítségével hívja meg a
sh
-et, a{}
-et – tehát hol van a példám (az -okdir mellett) hordozható?find . -name "*.csv" -exec diff {} /some/other/path/{} ";" -print
- A „maszkolás” nem ' t általános terminológia a shell irodalomban, tehát ' meg kell magyaráznod, mire gondolsz, ha meg akarod érteni. Példám a
{}
-t csak egyszer és külön argumentumban használja; más esetek (kétszer vagy alszövegként használva) nem hordozhatók. A „hordozható” azt jelenti, hogy ' minden unix rendszeren működik; jó iránymutatás a POSIX / Single Unix specifikáció .
Válasz
Meglepődtem, hogy nem láttam readarray
említést. Ez nagyon megkönnyíti, ha a <<<
operátor:
$ touch oneword "two words" $ readarray -t files <<<"$(ls)" $ for file in "${files[@]}"; do echo "|$file|"; done |oneword| |two words|
A <<<"$expansion"
konstrukció használata lehetővé teszi az új sorokat tartalmazó változók tömbökre bontását is, például :
$ string=$(dmesg) $ readarray -t lines <<<"$string" $ echo "${lines[0]}" [ 0.000000] Initializing cgroup subsys cpuset
readarray
már évek óta a Bash-ban van, ezért valószínűleg ennek kellene lennie ezt a Bash-ban.
Válasz
Az Afaik megtalálásához minden szükséges.
find . -okdir diff {} /some/other/path/{} ";"
a find gondoskodik a programok kíméletes meghívásáról. Az -okdir a diff előtt kéri (biztos vagy benne, hogy igen / nem).
Nincs benne shell, nincs globbing, joker, pi, pa, po.
Sidenote-ként: Ha a keresést kombinálod a / while / do / xargs fájlhoz, a legtöbb esetben y rosszul csinálod. 🙂
Megjegyzések
- Köszönöm a választ. Miért csinálod rosszul, ha a keresést kombinálod a / while / do / xargs fájlhoz?
- Már megismételheted az állományok egy részhalmazát. A legtöbb kérdéssel megjelenő ember csak az egyik műveletet (-ok (dir) -exec (dir), -delete) használhatja kombinációban, amely "; " vagy + (később párhuzamos meghíváshoz). Ennek fő oka az, hogy nem ' nem kell fájlparaméterekkel babrálnia, maszkolva őket a héj számára. Nem is olyan fontos: Nem kell ' állandóan új folyamatokat, kevesebb memóriát, nagyobb sebességet biztosítani. rövidebb program.
- Nem azért, hogy összetörje a szellemét, hanem hasonlítsa össze:
time find -type f -exec cat "{}" \;
atime find -type f -print0 | xargs -0 -I stuff cat stuff
-vel. Axargs
verzió 11 másodperccel gyorsabb volt, amikor 10000 üres fájlt dolgozott fel. Legyen óvatos, amikor azt állítja, hogy afind
és más segédprogramok kombinálása a legtöbb esetben helytelen.-print0
és-0
azért vannak, hogy a fájlnevekben szóközöket kezeljenek úgy, hogy szóköz helyett nulla bájtot használnak elemelválasztóként. - @JonathanKomar: A find / exec kommandó 11,7 másodpercet vett igénybe a rendszeremen 10 000 fájllal, az xargs 9,7 s verzióval,
time find -type f -exec cat {} +
, ahogy az előző kommentemben javasoltam, 0,1 s. Vegye figyelembe a " hibás " és a " ' rosszul csinálja ", különösen, ha smiley-val díszítik. Például rosszul tette? 😉 BTW, a fájlnévben lévő szóközök nem jelentenek problémát a fenti parancs számára, és általában megtalálhatók. Rakománykultusz programozó? Egyébként a keresés más eszközökkel való kombinálása rendben van, csak az xargs a legtöbbször szuperflous. - @userunknown Elmagyaráztam, hogy a kódom hogyan foglalkozik az utókor számára fenntartott terekkel (a jövő nézőinek oktatása), és nem jelenti azt, hogy a kódod nem. A párhuzamos hívások esetén a
+
nagyon gyors, ahogy említetted. Nem mondanám a cargo kultikus programozót, mert axargs
ilyen módon történő használatának lehetősége számos alkalommal jól jön. Inkább egyetértek a Unix filozófiájával: csinálj egy dolgot és csináld jól (használd a programokat külön-külön vagy együttesen a munka elvégzéséhez).find
finom vonalat sétál ott.
Válasz
Húzza végig az összes fájlt ( bármilyen speciális karaktert tartalmaz) a teljesen biztonságos megtalálás (lásd a linket a dokumentációhoz):
exec 9< <( find "$absolute_dir_path" -type f -print0 ) while IFS= read -r -d "" -u 9 do file_path="$(readlink -fn -- "$REPLY"; echo x)" file_path="${file_path%x}" echo "START${file_path}END" done
Megjegyzések
- Köszönjük, hogy megemlítette
-d ''
. Nem tudtam, hogy ' nem vettem észre, hogy a$'\0'
ugyanaz volt, mint a''
, de úgy tűnik, hogy lenni. Jó megoldás is. - Szeretem a lelet és a köszönet elválasztását, köszönöm.
Válasz
Meglepődtem, hogy itt még senki sem említette a nyilvánvaló zsh
megoldást:
for file (**/*.csv(ND.)) { do-something-with $file }
((D)
rejtett fájlok is, (N)
a hiba elkerülése érdekében, ha nincs egyezés, (.)
rendes fájlokra korlátozva.)
bash4.3
és a fentiek már részben támogatják is:
shopt -s globstar nullglob dotglob for file in **/*.csv; do [ -f "$file" ] || continue [ -L "$file" ] && continue do-something-with "$file" done
Válasz
A fájlnevek, amelyekben szóköz van, több névnek tűnnek a parancssorban, ha ” Ha a fájlod neve “Hello World.txt”, a diff sor a következőre bővül:
diff Hello World.txt /some/other/path/Hello World.txt
amely négy fájlnévnek tűnik. idézetek az argumentumok körül:
diff "$file" "/some/other/path/$file"
Megjegyzések
- Ez segít, de nem ' nem oldja meg a problémámat. Még mindig látok olyan eseteket, amikor a fájlt több tokenné osztják fel.
- Ez a válasz félrevezető. A probléma a
for file in `find . -name "*.csv"`
parancs. Ha van egyHello World.csv
nevű fájl, akkor afile
fájl értéke./Hello
, majdWorld.csv
. A (z)$file
idézés nem kapott segítséget '.
Válasz
A kettős idézés a barátod.
diff "$file" "/some/other/path/$file"
Ellenkező esetben a változó tartalma szó szerint tagolódik.
Megjegyzések
- Ez félrevezető. A probléma a
for file in `find . -name "*.csv"`
paranccsal történik. Ha van egyHello World.csv
,file
értéke./Hello
, majdWorld.csv
. A (z)$file
idézettel nem nyer ' segítséget.
Válasz
A bash4 használatával a beépített mapfile függvény segítségével beállíthatja az egyes sorokat tartalmazó tömböt, és iterálhat ezen a tömbön.
Válasz
Az értékekben lévő szóközök elkerülhetők a cikluskonstrukcióhoz szükséges egyszerű
for CHECK_STR in `ls -l /root/somedir` do echo "CHECKSTR $CHECK_STR" done
ls -l gyökér / somedir c a fájlomat szóközökkel tartalmazza
A fájlom fölötti szóköz szóközzel
ennek a kimenetnek az elkerülése érdekében, egyszerű megoldás (vegye figyelembe a dupla idézőjeleket)
for CHECK_STR in "`ls -l /root/somedir`" do echo "CHECKSTR $CHECK_STR" done
a fájlomat szóközökkel adja ki
megpróbálta a bash-on
Megjegyzések
- „Fájlok közötti áttekintés ”- ezt mondja a kérdés. Megoldása a teljes
ls -l
kimenetet egyszerre fogja kiadni . Ez gyakorlatilag egyenértékű a következővel:echo "CHECKSTR `ls -l /root/somedir`"
.