Hurok a fájlokban, amelyekben szóköz van a nevekben? [duplicate]

Erre a kérdésre már itt vannak válaszok :

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:

  1. Alapértelmezés szerint a héj felosztja a parancs kimenetét szóközökön, füleken és új sorokban
  2. A fájlnevek helyettesítő karaktereket tartalmazhatnak, amelyek kibővülne
  3. 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 és read 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:

  1. hajtsa végre a find parancsot, ragadja meg a kimenetét.
  2. A find kimenetet ossza szét külön szavakra. Bármely szóköz karakter szóelválasztó.
  3. 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.

  1. A find parancs visszaadja a ./foo* bar.csv parancsot.
  2. A héj felosztja ezt a karakterláncot a szóköznél, két szót előállítva: ./foo* és bar.csv.
  3. Mivel ./foo* egy fénylő metakaraktert tartalmaz, kibővül a megfelelő fájlok listájával: ./foo 1.txt és ./foo 2.txt.
  4. Ezért a for ciklust egymás után hajtják végre ./foo 1.txt, ./foo 2.txt és bar.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 az find -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 az find -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 a echo 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 a read 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 "{}" \; a time find -type f -print0 | xargs -0 -I stuff cat stuff -vel. A xargs verzió 11 másodperccel gyorsabb volt, amikor 10000 üres fájlt dolgozott fel. Legyen óvatos, amikor azt állítja, hogy a find é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 a xargs 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 egy Hello World.csv nevű fájl, akkor a file fájl értéke ./Hello, majd World.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 egy Hello World.csv, file értéke ./Hello, majd World.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`".

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük