Chwytając wyjście, by znaleźć. - print0 do tablicy bash


Używając
find. -print0
wydaje się być jedynym bezpiecznym sposobem uzyskania listy plików w bashu ze względu na możliwość nazw plików zawierających spacje, znaki nowej linii, cudzysłowy itp.
Jednak ciężko mi jest sprawić, by wynik polecenia find był użyteczny w bashu lub w innych narzędziach wiersza poleceń. Jedynym sposobem, w jaki udało mi się użyć wyjścia, jest potokowanie go do Perla i zmiana IFS perla na null:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

Ten przykład wypisuje liczbę znalezionych plików, unikając niebezpieczeństwa, że ​​znaki nowej linii w nazwach plików zepsują licznik, tak jak w przypadku:
find . | wc -l

Ponieważ większość programów wiersza poleceń nie obsługuje danych wejściowych rozdzielanych zerami, uważam, że najlepiej jest przechwytywać dane wyjściowe polecenia
find. -print0
do tablicy bash, tak jak to zrobiłem w powyższym fragmencie perla, a następnie kontynuuj zadanie, cokolwiek to jest.
W jaki sposób mogę to zrobić?
To nie działa:
find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

O wiele bardziej ogólne pytanie może brzmieć tak:

jak mogę zrobić użyteczne rzeczy z listami plików w bash?

Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Bezwstydnie skradziony

Greg BashFAQ
http://mywiki.wooledge.org/BashFAQ/020
:
unset a i
while IFS= read -r -d $'\0' file; do
a[i++]="$file" # or however you want to process each file
done < <(find/tmp -type f -print0)

Zwróć uwagę, że zastosowana tutaj konstrukcja przekierowania (
cmd1 & < & < (cmd2)
) jest podobna, ale nie dokładnie taka sama, jak bardziej konwencjonalny potok (
cmd2 | cmd1
) - jeśli polecenia są poleceniami wbudowanymi powłoki (np.
while
), wersja potoku wykonuje je w podsiatkach, a wszelkie ustawione przez nie zmienne (takie jak tablica
a
) są tracone przy wyjściu . cmd1 & < & < (cmd2) uruchamia cmd2 tylko na podsiatce, więc tablica żyje po zbudowaniu. Ostrzeżenie: ta forma przekierowania jest dostępna tylko w bash, nawet bash w trybie emulacji sh; musisz uruchomić swój skrypt z
#!/bin/bash
.
Ponadto, ponieważ krok przetwarzania pliku (w tym przypadku po prostu
a [i ++] = "$ file"
, ale możesz zrobić coś bardziej wymyślnego bezpośrednio w pętli) przekierowuje jego wejście, nie może używać żadnych poleceń, które można odczytać ze standardowego wejścia. Aby uniknąć tego ograniczenia, zwykle używam:
unset a i
while IFS= read -r -u3 -d $'\0' file; do
a[i++]="$file" # or however you want to process each file
done 3< <(find/tmp -type f -print0)
... który przekazuje listę plików przez blok 3, a nie stdin.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Może szukasz xargs:
find . -print0 | xargs -r0 do_something_useful

Opcja -L 1 może być również przydatna dla ciebie, która sprawia, że ​​xargs exec do_something_useful ma tylko 1 argument plikowy.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Głównym problemem jest to, że separator NUL (\ 0) jest tutaj bezużyteczny, ponieważ nie jest możliwe przypisanie IFS do NUL. Dlatego jako dobrzy programiści upewniamy się, że dane wejściowe do naszego programu są czymś, co może obsłużyć.
Najpierw tworzymy mały program, który wykonuje za nas tę część:
#!/bin/bash
printf "%s" "$@" | base64
... i nazwij go base64str (nie zapomnij o chmod + x)
Po drugie, możemy teraz użyć prostej i bezpośredniej pętli for:
for i in `find -type f -exec base64str '{}' \;`
do
file="`echo -n "$i" | base64 -d`"
# do something with file
done

Tak więc sztuczka polega na tym, że ciąg base64 jest bez znaku, co powoduje problemy dla basha - oczywiście xxd lub coś podobnego może również wykonać zadanie.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Od wersji Bash 4.4, wbudowany
mapfile
ma przełącznik
-d
(aby określić separator podobny do przełącznika
-d
w
przeczytaj
), a separatorem może być bajt o wartości null. Stąd dobra odpowiedź na tytułowe pytanie

Przechwytywanie danych wyjściowych funkcji
find. -print0
do tablicy bash

jest:
mapfile -d '' ary < <(find . -print0)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Inny sposób liczenia plików:
find/DIR -type f -print0 | tr -dc '\0' | wc -c
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możesz bezpiecznie liczyć na to:
find . -exec echo ';' | wc -l

(Drukuje nową linię dla każdego znalezionego pliku/katalogu, a następnie zlicza wydrukowane znaki nowej linii ...)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Myślę, że są bardziej eleganckie rozwiązania, ale z tego zrezygnuję. Działa to również w przypadku nazw plików ze spacjami i/lub znakami nowej linii:
i=0;
for f in *; do
array[$i]="$f"
((i++))
done

Następnie możesz na przykład wyświetlić listę plików jeden po drugim (w tym przypadku w odwrotnej kolejności):
for ((i = $i - 1; i >= 0; i--)); do
ls -al "${array[$i]}"
done

Ta strona
http://bash.cyberciti.biz/file ... file/
podaje dobry przykład, a więcej informacji znajdziesz w

Rozdział 26
http://tldp.org/LDP/abs/html/arrays.html
W instrukcji

Advanced Bash-Scripting Guide
http://tldp.org/LDP/abs/html/
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie rozmiar, jeśli to możliwe:
man ruby | less -p 777 
IFS=$'\777'
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) )
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) )
echo ${#array[@]}
printf "%s\n" "${array[@]}" | nl
echo "${array[0]}"
IFS=$' \t\n'
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jestem nowicjuszem, ale wierzę, że to jest odpowiedź; mam nadzieję, że to komuś pomoże:
STYLE="$HOME/.fluxbox/styles/"declare -a array1LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`
echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jest to podobne do wersji Stephan202, ale pliki (i katalogi) są umieszczane w tablicy jednocześnie. Pętla
for
służy tutaj tylko do „robienia przydatnych rzeczy”:
files=(*) # put files in current directory into an array
i=0
for file in "${files[@]}"
do
echo "File ${i}: ${file}" # do something useful
let i++
done

Aby otrzymać fakturę:
echo ${#files[@]}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Stare pytanie, ale nikt nie zaproponował tej prostej metody, więc pomyślałem, że to zrobię. Oczywiście, jeśli twoje nazwy plików to ETX, nie rozwiązuje to twojego problemu, ale podejrzewam, że działa w każdym scenariuszu w świecie rzeczywistym. Próba użycia wartości null wydaje się być sprzeczna z domyślnymi regułami obsługi IFS. Dostosuj go do swoich upodobań dzięki opcjom wyszukiwania i obsługi błędów.
savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Odpowiedź Gordona Davissona jest świetna na bash. Istnieje jednak przydatny skrót dla użytkowników zsh:
Najpierw umieść ciąg w zmiennej:
A="$(find/tmp -type f -print0)"

Następnie usuń tę zmienną i zapisz ją w tablicy:
B=( ${(s/^@/)A} )

Jest jedna sztuczka:
^ @
to znak NUL. Aby to zrobić, musisz nacisnąć Ctrl + V, a następnie Ctrl + @.
Możesz sprawdzić, czy każdy rekord $ B zawiera poprawną wartość:
for i in "$B[@]"; echo \"$i\"

Wnikliwi czytelnicy zauważą, że wywołania polecenia
find
w większości przypadków można uniknąć, używając składni
**
. Na przykład:
B=(/tmp/** )
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Bash nigdy nie radził sobie dobrze z nazwami plików (ani żadnym innym tekstem), ponieważ używa spacji jako separatora listy.
Zamiast tego zalecałbym używanie Pythona z biblioteką sh.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się