Rekurencyjny skrypt bash do zbierania informacji o każdym pliku w strukturze katalogów
14
Jak pracować rekurencyjnie przez drzewo katalogów i wykonać określoną komendę dla każdego pliku, i wysyłać ścieżkę, nazwę pliku, rozszerzenie, rozmiar pliku i jakiś inny określony tekst do jednego pliku w bash.
lol, dzięki za edycję; Będę pierwszym, który przyzna, że komplikuję sprawy, ponieważ przyzwyczajono mnie do 800 nieistotnych pytań w świecie hooman; więc próbuję odpowiedzieć na oczywiste pytania; nauczę się jednak :-)
SPooKYiNeSS
1
OK, myślę, że pytanie jest dość jasne, co należy zrobić, przejdź do drzewa katalogów i wyślij informacje o każdym pliku. Pytanie jest dość jasne i sądząc po ilości odpowiedzi, ludzie rozumieją je dość dobrze. 3 głosy za niejasność naprawdę nie zasługują na to pytanie
Sergiy Kolodyazhnyy
Odpowiedzi:
16
Chociaż findrozwiązania są proste i wydajne, postanowiłem stworzyć bardziej skomplikowane rozwiązanie, oparte na tej interesującej funkcji , którą zobaczyłem kilka dni temu.
Więcej objaśnień i dwa inne skrypty, oparte na bieżących, znajdują się tutaj .
1. Utwórz plik skryptu wykonywalnego o nazwie walk, który znajduje się w, /usr/local/binaby był dostępny jako polecenie powłoki:
Skopiuj poniższą treść skryptu i użyj w nano: Shift+ Insertdo wklejania; Ctrl+ Oi Enterdla oszczędności; Ctrl+ Xdo wyjścia.
2. Treść skryptu walkto:
#!/bin/bash# Colourise the output
RED='\033[0;31m'# Red
GRE='\033[0;32m'# Green
YEL='\033[1;33m'# Yellow
NCL='\033[0m'# No Color
file_specification(){
FILE_NAME="$(basename "${entry}")"
DIR="$(dirname "${entry}")"
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
SIZE="$(du -sh "${entry}" | cut -f1)"
printf "%*s${GRE}%s${NCL}\n" $((indent+4))''"${entry}"
printf "%*s\tFile name:\t${YEL}%s${NCL}\n" $((indent+4))''"$FILE_NAME"
printf "%*s\tDirectory:\t${YEL}%s${NCL}\n" $((indent+4))''"$DIR"
printf "%*s\tName only:\t${YEL}%s${NCL}\n" $((indent+4))''"$NAME"
printf "%*s\tExtension:\t${YEL}%s${NCL}\n" $((indent+4))''"$EXT"
printf "%*s\tFile size:\t${YEL}%s${NCL}\n" $((indent+4))''"$SIZE"}
walk(){local indent="${2:-0}"
printf "\n%*s${RED}%s${NCL}\n\n""$indent"''"$1"# If the entry is a file do some operationsfor entry in"$1"/*;do[[-f "$entry"]]&& file_specification;done# If the entry is a directory call walk() == create recursionfor entry in"$1"/*;do[[-d "$entry"]]&& walk "$entry" $((indent+4));done}# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()[[-z "${1}"]]&& ABS_PATH="${PWD}"|| cd "${1}"&& ABS_PATH="${PWD}"
walk "${ABS_PATH}"
echo
3. Objaśnienie:
Główny mechanizm tej walk()funkcji dość dobrze opisuje Zanna w swojej odpowiedzi . Opiszę więc tylko nową część.
W ramach walk()funkcji dodałem tę pętlę:
for entry in"$1"/*;do[[-f "$entry"]]&& file_specification;done
Oznacza to, że dla każdego $entrypliku będzie wykonywana funkcja file_specification().
Funkcja file_specification()składa się z dwóch części. Pierwsza część otrzymuje dane związane z plikiem - nazwa, ścieżka, rozmiar itp. Druga część generuje dane w dobrze sformatowanej formie. Do sformatowania danych służy polecenie printf. A jeśli chcesz ulepszyć skrypt, powinieneś przeczytać o tym poleceniu - na przykład w tym artykule .
Funkcja file_specification()jest dobrym miejscem, w którym można umieścić określone polecenie, które należy wykonać dla każdego pliku . Użyj tego formatu:
polecenie „$ {entry}”
Lub możesz zapisać wynik polecenia jako zmienną, a następnie printftę zmienną itp .:
MY_VAR = "$ ( polecenie " $ {entry} ")"
printf "% * s \ t Rozmiar pliku: \ t $ {YEL}% s $ {NCL} \ n" $ ((wcięcie + 4)) '' „$ MY_VAR”
Lub bezpośrednio printfwynik polecenia:
printf "% * s \ t Rozmiar pliku: \ t $ {YEL}% s $ {NCL} \ n" $ ((wcięcie + 4)) '' „$ ( polecenie „ $ {entry} ”)”
Sekcja zwana żebraniem Colourise the outputinicjuje kilka zmiennych, które są używane w printfpoleceniu do kolorowania danych wyjściowych. Więcej na ten temat można znaleźć tutaj .
Do dolnej części skryptu dodano dodatkowy warunek, który dotyczy ścieżek bezwzględnych i względnych.
4. Przykłady użycia:
Aby uruchomić walkbieżący katalog:
walk # You shouldn't use any argument,
walk ./# but you can use also this format
Aby uruchomić walkdowolny katalog podrzędny:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
Aby uruchomić walkdla dowolnego innego katalogu:
walk /full/path/to/<directory name>
Aby utworzyć plik tekstowy na podstawie danych walkwyjściowych:
walk > output.file
Aby utworzyć plik wyjściowy bez kodów kolorów ( źródło ):
walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g"> output.file
Jakiego procesu używasz do stworzenia tych gifów @ pa4080?
pbhj
@pbhj, pod Ubuntu używam Peek , jest prosty i przyjemny, ale czasami ulega awarii i nie ma możliwości edycji. Większość moich plików GIF jest tworzona w systemie Windows, w którym nagrywam okno połączenia VNC. Mam osobną maszynę stacjonarną, której używam głównie do tworzenia MS Office i GIF-ów :) Narzędziem, którego tam używam, jest ScreenToGif . Jest otwarty, darmowy i ma potężny edytor i mechanizmy przetwarzania. Niestety nie mogę znaleźć narzędzia takiego jak ScreenToGif dla Ubuntu.
pa4080
13
Jestem nieco zakłopotany, dlaczego nikt jeszcze tego nie opublikował, ale rzeczywiście bashma możliwości rekurencyjne, jeśli włączysz globstaropcję i użyjesz **glob. Jako taki, możesz napisać (prawie) czysty bash skrypt, który używa tej rekurencyjnej globstar w następujący sposób:
#!/usr/bin/env bash
shopt -s globstar
for i in./**/*doif[-f "$i"];then
printf "Path: %s\n""${i%/*}"# shortest suffix removal
printf "Filename: %s\n""${i##*/}"# longest prefix removal
printf "Extension: %s\n""${i##*.}"
printf "Filesize: %s\n""$(du -b "$i" | awk '{print $1}')"# some other command can go here
printf "\n\n"fidone
Zauważ, że tutaj używamy rozszerzania parametrów, aby uzyskać pożądane części nazwy pliku i nie polegamy na poleceniach zewnętrznych, z wyjątkiem uzyskiwania rozmiaru pliku dui czyszczenia wyniku awk.
Gdy przegląda drzewo katalogów, dane wyjściowe powinny wyglądać mniej więcej tak:
Path:./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize:326
chmod +x ./myscript.shObowiązują standardowe zasady korzystania ze skryptu: upewnij się, że jest wykonywalny i uruchom go z bieżącego katalogu przez ./myscript.shlub umieść w ~/bini uruchom source ~/.profile.
Jeśli drukujesz pełną nazwę pliku, jakie dodatkowe „rozszerzenie” daje ci? Być może naprawdę chcesz informacji MIME, które "$(file "$i")"(w powyższym skrypcie jako druga część printf) powrócą?
pbhj
1
@pbhj Dla mnie osobiście? Nic. Ale OP, który zadał zadane pytanie output the path, filename, extension, filesize , więc odpowiedź odpowiada zadanemu pytaniu. :)
Sergiy Kolodyazhnyy
12
Możesz użyć finddo wykonania pracy
find /path/-type f -exec ls -alh {} \;
Pomoże Ci to, jeśli chcesz tylko wyświetlić listę wszystkich plików o rozmiarze.
-execpozwoli ci wykonać niestandardowe polecenie lub skrypt dla każdego pliku
\;użytego do parsowania plików jeden po drugim, możesz użyć, +;jeśli chcesz je połączyć (oznacza nazwy plików).
Elegancki i prosty (jeśli wiesz o tym find -printf). +1
David Foerster
1
Jeśli wiesz, jak głębokie jest drzewo, najłatwiejszym sposobem będzie użycie symbolu wieloznacznego *.
Napisz wszystko, co chcesz zrobić, jako skrypt powłoki lub funkcja
function thing(){...}
następnie uruchomić for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... etc
W ramach funkcji / skryptu możesz użyć kilku prostych testów, aby wyodrębnić pliki, z którymi chcesz pracować, i zrobić z nimi wszystko, czego potrzebujesz.
„to nie zadziała, jeśli którakolwiek z twoich nazw plików ma spacje” ... ponieważ zapomniałeś podać swoje zmienne! Użyj „$ i” zamiast $i.
muru
@muru nie, powodem, dla którego nie działa, jest to, że pętla „for” dzieli się na spacje - „ / ” zostaje rozwinięta w listę wszystkich plików oddzieloną spacjami. Możesz obejść ten problem, np. przez bałagan w IFS, ale w tym momencie równie dobrze możesz po prostu użyć find.
Benubird,
@ pa4080 nie dotyczy tej odpowiedzi, ale i tak wygląda to bardzo przydatne, dzięki!
Benubird,
Myślę, że nie rozumiesz, jak to for i in */*działa. Tutaj przetestuj:for i in */*; do printf "|%s|\n" "$i"; done
Odpowiedzi:
Chociaż
find
rozwiązania są proste i wydajne, postanowiłem stworzyć bardziej skomplikowane rozwiązanie, oparte na tej interesującej funkcji , którą zobaczyłem kilka dni temu.1. Utwórz plik skryptu wykonywalnego o nazwie
walk
, który znajduje się w,/usr/local/bin
aby był dostępny jako polecenie powłoki:nano
: Shift+ Insertdo wklejania; Ctrl+ Oi Enterdla oszczędności; Ctrl+ Xdo wyjścia.2. Treść skryptu
walk
to:3. Objaśnienie:
Główny mechanizm tej
walk()
funkcji dość dobrze opisuje Zanna w swojej odpowiedzi . Opiszę więc tylko nową część.W ramach
walk()
funkcji dodałem tę pętlę:Oznacza to, że dla każdego
$entry
pliku będzie wykonywana funkcjafile_specification()
.Funkcja
file_specification()
składa się z dwóch części. Pierwsza część otrzymuje dane związane z plikiem - nazwa, ścieżka, rozmiar itp. Druga część generuje dane w dobrze sformatowanej formie. Do sformatowania danych służy polecenieprintf
. A jeśli chcesz ulepszyć skrypt, powinieneś przeczytać o tym poleceniu - na przykład w tym artykule .Funkcja
file_specification()
jest dobrym miejscem, w którym można umieścić określone polecenie, które należy wykonać dla każdego pliku . Użyj tego formatu:Lub możesz zapisać wynik polecenia jako zmienną, a następnie
printf
tę zmienną itp .:Lub bezpośrednio
printf
wynik polecenia:Sekcja zwana żebraniem
Colourise the output
inicjuje kilka zmiennych, które są używane wprintf
poleceniu do kolorowania danych wyjściowych. Więcej na ten temat można znaleźć tutaj .Do dolnej części skryptu dodano dodatkowy warunek, który dotyczy ścieżek bezwzględnych i względnych.
4. Przykłady użycia:
Aby uruchomić
walk
bieżący katalog:Aby uruchomić
walk
dowolny katalog podrzędny:Aby uruchomić
walk
dla dowolnego innego katalogu:Aby utworzyć plik tekstowy na podstawie danych
walk
wyjściowych:Aby utworzyć plik wyjściowy bez kodów kolorów ( źródło ):
5. Demonstracja użytkowania:
źródło
Jestem nieco zakłopotany, dlaczego nikt jeszcze tego nie opublikował, ale rzeczywiście
bash
ma możliwości rekurencyjne, jeśli włączyszglobstar
opcję i użyjesz**
glob. Jako taki, możesz napisać (prawie) czystybash
skrypt, który używa tej rekurencyjnej globstar w następujący sposób:Zauważ, że tutaj używamy rozszerzania parametrów, aby uzyskać pożądane części nazwy pliku i nie polegamy na poleceniach zewnętrznych, z wyjątkiem uzyskiwania rozmiaru pliku
du
i czyszczenia wynikuawk
.Gdy przegląda drzewo katalogów, dane wyjściowe powinny wyglądać mniej więcej tak:
chmod +x ./myscript.sh
Obowiązują standardowe zasady korzystania ze skryptu: upewnij się, że jest wykonywalny i uruchom go z bieżącego katalogu przez./myscript.sh
lub umieść w~/bin
i uruchomsource ~/.profile
.źródło
"$(file "$i")"
(w powyższym skrypcie jako druga część printf) powrócą?output the path, filename, extension, filesize
, więc odpowiedź odpowiada zadanemu pytaniu. :)Możesz użyć
find
do wykonania pracyPomoże Ci to, jeśli chcesz tylko wyświetlić listę wszystkich plików o rozmiarze.
-exec
pozwoli ci wykonać niestandardowe polecenie lub skrypt dla każdego pliku\;
użytego do parsowania plików jeden po drugim, możesz użyć,+;
jeśli chcesz je połączyć (oznacza nazwy plików).źródło
Z
find
tylko.Lub możesz zamiast tego użyć poniżej:
źródło
find -printf
). +1Jeśli wiesz, jak głębokie jest drzewo, najłatwiejszym sposobem będzie użycie symbolu wieloznacznego
*
.Napisz wszystko, co chcesz zrobić, jako skrypt powłoki lub funkcja
następnie uruchomić
for i in *; do thing "$i"; done
,for i in */*; do thing "$i"; done
... etcW ramach funkcji / skryptu możesz użyć kilku prostych testów, aby wyodrębnić pliki, z którymi chcesz pracować, i zrobić z nimi wszystko, czego potrzebujesz.
źródło
$i
.for i in */*
działa. Tutaj przetestuj:for i in */*; do printf "|%s|\n" "$i"; done
find
można to zrobić:Sprawdź
man find
inne właściwości pliku.Jeśli naprawdę potrzebujesz rozszerzenia, możesz dodać to:
źródło