Chcę iterować listę plików. Ta lista jest wynikiem find
polecenia, więc wymyśliłem:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
Jest w porządku, chyba że plik ma spacje w nazwie:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
Co mogę zrobić, aby uniknąć podziału na spacje?
Odpowiedzi:
Możesz zastąpić iterację opartą na słowach iteracją opartą na wierszach:
źródło
touch "$(printf "foo\nbar")"
IFS= while read -r f
zamiast tego.find
i pętlę while.-exec
będzie czystsze niż wyraźnego pętli:find . -iname "foo*" -exec echo "File found: {}" \;
. Dodatkowo, w wielu przypadkach można zastąpić ten ostatni\;
ze+
umieścić wiele plików w jednym poleceniu.Istnieje kilka praktycznych sposobów na osiągnięcie tego.
Jeśli chcesz ściśle trzymać się oryginalnej wersji, możesz to zrobić w ten sposób:
To nadal nie powiedzie się, jeśli w nazwach plików znajdują się dosłowne znaki nowego wiersza, ale spacje go nie złamią.
Jednak bałagan w IFS nie jest konieczny. Oto mój preferowany sposób:
Jeśli
< <(command)
składnia jest dla Ciebie nieznana, powinieneś przeczytać o podstawianiu procesów . Zaletą tegofor file in $(find ...)
jest to, że pliki ze spacjami, znakami nowej linii i innymi znakami są poprawnie obsługiwane. Działa to, ponieważfind
z-print0
użyjenull
(aka\0
) jako terminatora dla każdej nazwy pliku i, w przeciwieństwie do newline, null nie jest prawnym znakiem w nazwie pliku.Przewaga tego nad prawie równoważną wersją
Czy to zachowanie dowolnego przypisania zmiennej w treści pętli while. Oznacza to, że jeśli potokujesz w taki sposób,
while
jak powyżej, to ciałowhile
jest w podpowłoce, co może nie być tym, czego chcesz.Zaleta wersji zastępującej proces
find ... -print0 | xargs -0
jest minimalna:xargs
wersja jest w porządku, jeśli wystarczy wydrukować linię lub wykonać jedną operację na pliku, ale jeśli trzeba wykonać wiele kroków, wersja pętli jest łatwiejsza.EDYCJA : Oto fajny skrypt testowy, abyś mógł zrozumieć różnicę między różnymi próbami rozwiązania tego problemu
źródło
$IFS
i< <(cmd)
składnia. Nadal jedno jest dla mnie niejasne, dlaczego$
w$'\0'
? Wielkie dzięki.while IFS= read
... do obsługi plików rozpoczynających się lub kończących na białych znakach.bash
więc czułem się bezpiecznie, korzystając z funkcji specyficznych dla bash. Podstawianie procesów nie jest przenośne dla innych powłok (sam sh najprawdopodobniej nigdy nie otrzyma tak znaczącej aktualizacji).IFS=$'\n'
zfor
zapobiega wewnętrznemu dzieleniu słów na linie, ale nadal powoduje, że wynikowe linie podlegają globowaniu, więc to podejście nie jest w pełni niezawodne (chyba że najpierw wyłączysz globowanie). Podczas pracyread -d $'\0'
działa nieco$'\0'
myląco , ponieważ sugeruje, że możesz użyć do tworzenia NUL-ów - nie możesz: a\0
w łańcuchu cytowanym w ANSI skutecznie przerywa łańcuch, więc-d $'\0'
jest to tak samo jak-d ''
.Istnieje również bardzo proste rozwiązanie: polegaj na globowaniu bash
Zauważ, że nie jestem pewien, czy to zachowanie jest domyślne, ale nie widzę żadnych specjalnych ustawień w moim shopt, więc powiedziałbym, że powinien być „bezpieczny” (testowany na OSX i Ubuntu).
źródło
źródło
Zobaczyć
man xargs
.źródło
Ponieważ nie używasz żadnego innego rodzaju filtrowania
find
, możesz użyć następujących opcji od wersjibash
4.0:**/
Pasuje zero lub więcej katalogów, więc pełny wzór będzie pasowałfoo*
w bieżącym katalogu lub jakichkolwiek podkatalogów.źródło
Bardzo lubię pętle i iteracje tablic, więc myślę, że dodam tę odpowiedź do miksu ...
Podobał mi się również głupi przykład pliku marchelbling. :)
W katalogu testowym:
Spowoduje to dodanie każdej linii listy plików do tablicy bash o nazwie
arr
z usuniętym końcowym znakiem nowej linii.Powiedzmy, że chcemy nadać tym plikom lepsze nazwy ...
$ {! arr [@]} rozwija się do 0 1 2, więc „$ {arr [$ i]}” jest i- tym elementem tablicy. Cytaty wokół zmiennych są ważne dla zachowania spacji.
Rezultatem są trzy pliki o zmienionych nazwach:
źródło
find
ma-exec
argument, który zapętla wyniki wyszukiwania i wykonuje dowolne polecenie. Na przykład:Tutaj
{}
reprezentuje znalezione pliki, a zawinięcie go""
pozwala wynikowemu poleceniu powłoki zająć się spacjami w nazwie pliku.W wielu przypadkach możesz zastąpić to ostatnie
\;
(które uruchamia nowe polecenie) znakiem\+
, który umieści wiele plików w jednym poleceniu (niekoniecznie wszystkie jednocześnie, zobaczman find
więcej szczegółów).źródło
W niektórych przypadkach, jeśli po prostu chcesz skopiować lub przenieść listę plików, możesz również przesłać tę listę do awk.
Ważne
\"" "\"
wokół pola$0
(w skrócie twoje pliki, jedna linia-lista = jeden plik).źródło
Ok - mój pierwszy post na Stack Overflow!
Chociaż moje problemy z tym zawsze były w csh, nie bash rozwiązanie, które przedstawię, na pewno zadziała w obu. Problem polega na interpretacji przez powłokę zwrotów „ls”. Możemy usunąć „ls” z problemu, po prostu używając rozszerzenia powłoki z
*
symboli wieloznacznych - ale daje to błąd „brak dopasowania”, jeśli w bieżącym (lub określonym folderze) nie ma plików - aby obejść ten problem, po prostu rozszerzamy rozszerzenie o pliki kropkowe w ten sposób:* .*
- zawsze da to wyniki, ponieważ pliki. i .. zawsze będzie obecny. Więc w csh możemy użyć tego konstruktu ...jeśli chcesz odfiltrować standardowe pliki kropek, jest to dość łatwe ...
Kod w pierwszym poście w tym wątku zostałby zapisany w następujący sposób: -
Mam nadzieję że to pomoże!
źródło
Kolejne rozwiązanie dla pracy ...
Celem było:
źródło