Jak analizować dane wyjściowe polecenia find, gdy w nazwach plików znajdują się spacje?


12

Korzystanie z pętli, takiej jak

for i in `find . -name \*.txt` 

pęknie, jeśli niektóre nazwy plików mają spacje.

Jakiej techniki mogę użyć, aby uniknąć tego problemu?


1
Pamiętaj, że w nazwie pliku mogą być także znaki nowej linii. Dlatego jest find -print0i xargs -0.
Daniel Beck

Odpowiedzi:


12

Idealnie nie robisz tego w ogóle, ponieważ prawidłowe analizowanie nazw plików w skrypcie powłoki jest zawsze trudne (napraw to spacje, nadal będziesz mieć problemy z innymi osadzonymi znakami, w szczególności nową linią). Jest to nawet wymienione jako pierwszy wpis na stronie BashPitfalls.

To powiedziawszy, istnieje sposób, aby prawie zrobić to, co chcesz:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Pamiętaj, aby cytować także $ipodczas korzystania z niego, aby uniknąć innych interpretacji spacji później. Pamiętaj również, aby $IFSzrezygnować po jego użyciu, ponieważ nieprzestrzeganie tego spowoduje później oszałamiające błędy.

Ma to jeszcze jedno zastrzeżenie: to, co dzieje się w whilepętli, może mieć miejsce w podpowłoce, w zależności od dokładnie używanej powłoki, więc ustawienia zmiennych mogą nie zostać zachowane. Wersja forpętli unika tego, ale za cenę, która nawet jeśli zastosujesz $IFSrozwiązanie w celu uniknięcia problemów ze spacjami, będziesz mieć kłopoty, jeśli findzwróci zbyt wiele plików.

W pewnym momencie poprawną poprawką do tego wszystkiego staje się robienie tego w języku takim jak Perl lub Python zamiast powłoki.


1
Podoba mi się pomysł używania Pythona, aby tego uniknąć.
Scott C Wilson,

12

Użyj find -print0i potokuj go xargs -0lub napisz własny mały program w języku C i potokuj go do małego programu w języku C. Po to -print0i -0wymyślono.

Skrypty powłoki nie są najlepszym sposobem na obsługę nazw plików ze spacjami: możesz to zrobić, ale robi się nieporęcznie.


Działa na mojej maszynie ^ TM!
Mcandre

2

Możesz ustawić „separator pola wewnętrznego” ( IFS) na coś innego niż miejsce na dzielenie argumentów pętli, np

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Resetuję IFSpo użyciu w find, głównie dlatego, że chyba ładnie wygląda. Nie widziałem żadnych problemów z ustawieniem go na nową linię, ale myślę, że jest to „czystsze”.

Inną metodą, zależnie od tego, co chcesz zrobić z danymi wyjściowymi find, jest albo bezpośrednie użycie -execz findpoleceniem, albo użycie -print0i wpięcie do niego xargs -0. W pierwszym przypadku finddba o ucieczkę nazwy pliku. W takim -print0przypadku findwypisuje dane wyjściowe z separatorem zerowym, a następnie xargsdzieli na to. Ponieważ żadna nazwa pliku nie może zawierać tego znaku (o czym wiem), jest to również zawsze bezpieczne. Jest to szczególnie przydatne w prostych przypadkach; i zwykle nie jest doskonałym zamiennikiem pełnej forpętli.


1

Korzystanie find -print0zxargs -0

Używanie w find -print0połączeniu z xargs -0jest całkowicie odporne na legalne nazwy plików i jest jedną z najbardziej rozszerzalnych dostępnych metod. Załóżmy na przykład, że chcesz wyświetlić listę wszystkich plików PDF w bieżącym katalogu. Mógłbyś pisać

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Znajduje każdy plik PDF (przez -iname '*.pdf') w bieżącym katalogu ( .) i dowolnym podkatalogu i przekazuje każdy z nich jako argument do echopolecenia. Ponieważ podaliśmy tę -n 1opcję, xargsprzekażemy tylko jeden argument na raz echo. Gdybyśmy pominęli tę opcję, xargsprzekazalibyśmy jej jak najwięcej echo. (Możesz echo short input | xargs --show-limitszobaczyć, ile bajtów jest dozwolonych w wierszu poleceń).

Co xargsdokładnie robi ?

Możemy wyraźnie zobaczyć, jaki wpływ xargsma na jego wejście - aw szczególności efekt -n- za pomocą skryptu, który powtarza swoje argumenty w sposób bardziej precyzyjny niż echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Pamiętaj, że doskonale radzi sobie ze spacjami i znakami nowej linii,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

co byłoby szczególnie kłopotliwe w przypadku następującego wspólnego rozwiązania:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Notatki

1

Nie zgadzam się z bashpodstawkami, ponieważ bashwraz z zestawem narzędzi * nix jest dość biegły w obsłudze plików (w tym tych, których nazwy mają osadzone białe znaki).

W rzeczywistości finddaje ci drobną kontrolę nad wyborem plików do przetworzenia ... Po stronie bash naprawdę musisz tylko zdać sobie sprawę, że musisz zrobić łańcuchy bash words; zazwyczaj przy użyciu „podwójnych cudzysłowów” lub innego mechanizmu, takiego jak IFS lub find's{}

Zauważ, że w większości / wielu sytuacjach nie musisz ustawiać i resetować IFS; po prostu użyj IFS lokalnie, jak pokazano w poniższych przykładach. Wszystkie trzy dobrze trzymają białe znaki. Także nie trzeba „standard” strukturę pętli, ponieważ znaleźć na \; to skutecznie pętla; wystarczy umieścić logikę pętli w funkcji bash (jeśli nie wywołujesz standardowego narzędzia).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

I jeszcze dwa przykłady

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'znajdź also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)


1
Obie perspektywy mają pewną ważność. Kiedy pracowałem tylko nad własnymi plikami, po prostu użyłem find i nie martwię się o to, ponieważ moje pliki nie mają spacji (ani znaków powrotu karetki!) W swoich nazwach. Ale kiedy zaczniesz pracować z plikami innych ludzi, musisz użyć bardziej niezawodnych technik.
Scott C Wilson,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.