(8 odpovědí)
Uzavřeno před 3 lety.
Napsal jsem následující skript, abych porovnal výstupy dvou adresářů se všemi stejnými soubory jako takovými:
#!/bin/bash
for file in `find . -name "*.csv"`  
do
     echo "file = $file";
     diff $file /some/other/path/$file;
     read char;
done
 Vím, že existují i jiné způsoby, jak toho dosáhnout. Je však zvláštní, že tento skript selže, když soubory obsahují mezery. Jak se s tím mohu vypořádat?
Příklad výstupu find:
./zQuery - abc - Do Not Prompt for Date.csv
 Přijatá odpověď:
Krátká odpověď (nejbližší vaší odpovědi, ale zpracovává mezery)
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"
 Lepší odpověď (zvládá také zástupné znaky a nové řádky v názvech souborů)
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
 Nejlepší odpověď (na základě Gillesovy odpovědi)
find . -type f -name '*.csv' -exec sh -c '
  file="$0"
  echo "$file"
  diff "$file" "/some/other/path/$file"
  read line </dev/tty
' exec-sh {} ';'
 
 Nebo ještě lépe, abyste se vyhnuli spuštění jednoho sh na soubor:
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 {} +
 Dlouhá odpověď
Máte tři problémy:
- Ve výchozím nastavení rozděluje shell výstup příkazu na mezery, tabulátory a nové řádky
 - Názvy souborů mohou obsahovat zástupné znaky, které by se rozšířily
 - Co když existuje adresář, jehož název končí na 
*.csv? 
1. Rozdělení pouze na nové řádky
 Chcete-li zjistit, co nastavit file shell musí převzít výstup z find a nějak to interpretovat, jinak file by byl pouze celý výstup find .
 Shell čte IFS proměnná, která je nastavena na <space><tab><newline> ve výchozím nastavení.
 Poté se podívá na každý znak ve výstupu find . Jakmile uvidí jakýkoli znak, který je v IFS , myslí si, že to označuje konec názvu souboru, takže nastaví file na jakékoli znaky, které dosud viděl, a spustí smyčku. Poté začne tam, kde skončil, aby získal název dalšího souboru, a spustí další smyčku atd., dokud nedosáhne konce výstupu.
Takže to efektivně dělá toto:
for file in "zquery" "-" "abc" ...
 Chcete-li mu sdělit, aby vstup rozdělil pouze na nové řádky, musíte to udělat
IFS=$'n'
 
 před vaším for ... find příkaz.
 Tím nastavíte IFS na jeden nový řádek, takže se rozděluje pouze na nové řádky a ne také na mezery a tabulátory.
 Pokud používáte sh nebo dash místo ksh93 , bash nebo zsh , musíte napsat IFS=$'n' místo toho takto:
IFS='
'
 To pravděpodobně stačí k tomu, aby váš skript fungoval, ale pokud máte zájem správně zpracovat některé další rohové případy, čtěte dále…
 2. Rozbalování $file bez zástupných znaků 
Uvnitř smyčky, kde děláte
diff $file /some/other/path/$file
 
 shell se pokusí rozbalit $file (znovu!).
 Může obsahovat mezery, ale protože jsme již nastavili IFS výše, zde to nebude problém.
 Může však také obsahovat zástupné znaky, například * nebo ? , což by vedlo k nepředvídatelnému chování. (Děkuji Gillesovi za upozornění.)
Chcete-li shellu říci, aby nerozšiřoval zástupné znaky, vložte proměnnou do dvojitých uvozovek, např.
diff "$file" "/some/other/path/$file"
 Stejný problém by nás také mohl kousnout
for file in `find . -name "*.csv"`
 Pokud jste například měli tyto tři soubory
file1.csv
file2.csv
*.csv
 (velmi nepravděpodobné, ale stále možné)
Související:Pokud změním oprávnění u souboru tar, bude to platit i pro soubory v něm?Bylo by to, jako byste běželi
for file in file1.csv file2.csv *.csv
 který se rozšíří na
for file in file1.csv file2.csv *.csv file1.csv file2.csv
 
 způsobující file1.csv a file2.csv zpracovat dvakrát.
Místo toho musíme udělat
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 čte řádky ze standardního vstupu, rozděluje řádek na slova podle IFS a uloží je pod názvy proměnných, které určíte.
 Zde mu říkáme, aby nerozděloval řádek na slova a uložil řádek do $file .
 Všimněte si také, že read line se změnil na read line </dev/tty .
 Je to proto, že uvnitř smyčky přichází standardní vstup z find prostřednictvím potrubí.
 Pokud jsme právě read , spotřebovalo by to část nebo celý název souboru a některé soubory by byly přeskočeny.
 /dev/tty je terminál, ze kterého uživatel spouští skript. Všimněte si, že to způsobí chybu, pokud je skript spuštěn přes cron, ale předpokládám, že to v tomto případě není důležité.
Co když pak název souboru obsahuje nové řádky?
 Můžeme to vyřešit změnou -print na -print0 a pomocí read -d '' na konci potrubí:
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
 
 To umožňuje find vložte nulový bajt na konec každého názvu souboru. Nulové bajty jsou jediné znaky, které nejsou v názvech souborů povoleny, takže by to mělo zpracovat všechny možné názvy souborů, bez ohledu na to, jak divné jsou.
 K získání názvu souboru na druhé straně použijeme IFS= read -r -d '' .
 Kde jsme použili read výše jsme použili výchozí oddělovač řádků nového řádku, ale nyní find používá jako oddělovač řádku null. V bash , nemůžete předat znak NUL v argumentu příkazu (ani vestavěným), ale bash rozumí -d '' ve významu odděleno NUL . Použijeme tedy -d '' aby read použijte stejný oddělovač řádků jako find . Všimněte si, že -d $'