GNU/Linux >> Znalost Linux >  >> Linux

Procházení souborů s mezerami v názvech?

Tato otázka zde již obsahuje odpovědi :Proč je opakování výstupu find špatným postupem?

(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:

  1. Ve výchozím nastavení rozděluje shell výstup příkazu na mezery, tabulátory a nové řádky
  2. Názvy souborů mohou obsahovat zástupné znaky, které by se rozšířily
  3. 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 $'

Linux
  1. Zkopírujte soubory v terminálu Linux

  2. Přesunout soubory v terminálu Linux

  3. Procházení obsahu souboru v Bash

  1. Najděte soubory a adresáře v Linuxu pomocí příkazu find

  2. Jak přejmenovat sadu souborů se vzorem?

  3. Používáte podtržítko v názvech souborů?

  1. Jak upravit systémové soubory pomocí editoru Kate?

  2. Linux - Nahrazení mezer v názvech souborů

  3. Iterujte seznam souborů s mezerami