Przechwytywanie wyników wyszukiwania. - print0 do tablicy bash

Użycie find . -print0 wydaje się być jedynym bezpiecznym sposobem uzyskania listy plików w bash ze względu na możliwość nazw plików zawierających spacje, nowe linie, cudzysłowy itp.

Jednak mam problem z tym, że wyjście find jest użyteczne w bash lub z innymi narzędziami wiersza poleceń. Jedynym sposobem, w jaki udało mi się wykorzystać wyjście, jest przekierowanie go do Perla i zmiana IFS Perla na null:

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'

Ten przykład wyświetla liczbę znalezionych plików, unikając niebezpieczeństwo, że nowe linie w nazwach plików będą psować liczbę, tak jak miałoby to miejsce w przypadku:

find . | wc -l

Ponieważ większość programów wiersza poleceń nie obsługuje danych wejściowych rozdzielanych null, uważam, że najlepszą rzeczą byłoby przechwycenie danych wyjściowych find . -print0 w tablicy bash, tak jak to zrobiłem w powyższym fragmencie Perla, a następnie kontynuowanie zadania, niezależnie od tego, jakie to będzie.

Jak mogę to zrobić?

To nie działa:

find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )

O wiele bardziej ogólne pytanie może brzmieć: Jak mogę robić przydatne rzeczy z listami plików w bash?

Author: Idris, 2009-07-13

13 answers

Bezwstydnie skradziony z Bashfaq Grega:

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)

Zauważ, że zastosowana tutaj konstrukcja przekierowująca (cmd1 < <(cmd2)) jest podobna, ale nie taka sama jak bardziej zwykły pipeline (cmd2 | cmd1) -- Jeśli polecenia są wbudowane w powłokę (np. while), to wersja pipeline wykonuje je w podshellach, a wszelkie zmienne, które ustawiają (np. Tablica a) są tracone po ich zakończeniu. cmd1 < <(cmd2) uruchamia tylko cmd2 w podkładzie, więc tablica żyje poza swoją konstrukcją. Uwaga: Ta forma przekierowania jest dostępne tylko w bash, nawet w trybie SH-emulacja; musisz uruchomić skrypt z #!/bin/bash.

Ponadto, ponieważ etap przetwarzania pliku (w tym przypadku po prostu a[i++]="$file", ale możesz chcieć zrobić coś bardziej fantazyjnego bezpośrednio w pętli) ma przekierowane wejście, nie może używać żadnych poleceń, które mogłyby 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 za pośrednictwem jednostki 3, a nie standardowego wejścia.

 95
Author: Gordon Davisson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-13 17:36:50

Może szukasz xargs:

find . -print0 | xargs -r0 do_something_useful

Opcja-L 1 może być również przydatna dla ciebie, co sprawia, że xargs exec do_something_useful ma tylko 1 argument pliku.

 7
Author: Balázs Pozsár,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-12 22:08:17

Główny problem polega na tym, że ogranicznik NUL (\0) jest tutaj bezużyteczny, ponieważ nie można przypisać IFS wartości NUL. Jako dobrzy Programiści dbamy więc o to, aby dane wejściowe dla naszego programu były czymś, z czym jest on w stanie sobie poradzić.

Najpierw tworzymy mały program, który wykonuje tę część za nas:

#!/bin/bash
printf "%s" "$@" | base64

...i nazwij to base64str (nie zapomnij chmod +x)

Po drugie możemy teraz użyć prostej i prostej pętli for:

for i in `find -type f -exec base64str '{}' \;`
do 
  file="`echo -n "$i" | base64 -d`"
  # do something with file
done

Więc sztuczka polega na tym, że Base64-string nie ma śladu, który powoduje problemy dla Basha - oczywiście xxd lub coś podobnego może również wykonać tę pracę.

 5
Author: zstegi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-29 20:18:32

Kolejny sposób liczenia Plików:

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-13 06:49:58

Myślę, że istnieją bardziej eleganckie rozwiązania, ale wrzucę to. Będzie to również działać dla nazw plików ze spacjami i / lub nowymi liniami:

i=0;
for f in *; do
  array[$i]="$f"
  ((i++))
done

Możesz np. wypisać pliki 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 daje ładny przykład, a więcej Zobacz Rozdział 26w Advanced Bash-Scripting Guide.

 1
Author: Stephan202,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-12 21:55:34

Możesz bezpiecznie policzyć za pomocą tego:

find . -exec echo ';' | wc -l

(wypisuje nową linię dla każdego znalezionego pliku / katalogu, a następnie policzy nowe linie wydrukowane...)

 1
Author: Balázs Pozsár,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-12 22:11:06

Unikaj xargsa, jeśli Możesz:

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' 
 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-13 08:36:24

Jestem nowy, ale wierzę, że to odpowiedź; mam nadzieję, że komuś pomoże:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-18 19:32:18

Jest to podobne do wersji Stephan202, ale pliki (i katalogi) są umieszczane w tablicy naraz. Pętla for służy 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 uzyskać liczbę:

echo ${#files[@]}
 0
Author: Dennis Williamson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-13 04:39:55

Stare pytanie, ale nikt nie zasugerował tej prostej metody, więc pomyślałem, że to zrobię. Przyznaję, jeśli Twoje nazwy plików mają ETX, to nie rozwiąże Twojego problemu, ale podejrzewam, że służy do każdego rzeczywistego scenariusza. Próba użycia null wydaje się być błędem domyślnych reguł obsługi IFS. Doprawić do gustu z opcji Znajdź i obsługi błędów.

savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
 0
Author: Dennis Simpson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-23 05:01:27

Odpowiedź Gordona Davissona jest świetna dla Basha. Jednak użyteczny Skrót istnieje dla użytkowników zsh:

Najpierw umieść łańcuch w zmiennej:

A="$(find /tmp -type f -print0)"

Następnie podziel tę zmienną i zapisz ją w tablicy:

B=( ${(s/^@/)A} )

Jest sztuczka: ^@ jest znakiem NUL. Aby to zrobić, musisz wpisać Ctrl + V, a następnie Ctrl+@.

Możesz sprawdzić każdy wpis $B zawiera właściwą wartość:

for i in "$B[@]"; echo \"$i\"

Uważni czytelnicy mogą zauważyć, że wywołanie find polecenia można uniknąć w większości przypadki z użyciem składni **. Na przykład:

B=( /tmp/** )
 0
Author: Jezz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-24 10:05:58

Od wersji Bash 4.4 wbudowany mapfile ma przełącznik -d (aby określić ogranicznik, podobny do przełącznika {[2] } instrukcji read), A ogranicznikiem może być bajt null. Stąd miła odpowiedź na pytanie w tytule

Przechwytywanie wyjścia find . -print0 do tablicy bash

Jest:

mapfile -d '' ary < <(find . -print0)
 0
Author: gniourf_gniourf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 15:37:59

Bash nigdy nie był dobry w obsłudze nazw plików (lub jakiegokolwiek tekstu), ponieważ używa spacji jako ogranicznika listy.

Zalecałbym używanie Pythona zamiast biblioteki sh.

 -1
Author: Timmmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-06 13:14:29