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.

SPOOKYiNeSS
źródło
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:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • 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 operations
        for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
        # If the entry is a directory call walk() == create recursion
        for 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

5. Demonstracja użytkowania:

wprowadź opis zdjęcia tutaj

pa4080
źródło
To dużo pracy, ale wygląda dobrze. Dobra robota !
Sergiy Kolodyazhnyy
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 ./**/*
do
    if [ -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"
    fi
done

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.

Sergiy Kolodyazhnyy
źródło
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).

Rajesh Rajendran
źródło
To miłe, ale nie odpowiada na wszystkie wymienione wymagania OP.
αғsнιη
1
@ αғsнιη Właśnie dałem mu szablon do pracy. Wiem, że nie jest to pełna odpowiedź na to pytanie, ponieważ uważam, że samo pytanie ma szeroki zakres.
Rajesh Rajendran
6

Z findtylko.

find /path/ -type f -printf "path:%h  fileName:%f  size:%kKB Some Text\n" > to_single_file

Lub możesz zamiast tego użyć poniżej:

find -type f -not -name "to_single_file"  -execdir sh -c '
    printf "%s %s %s %s Some Text\n" "$PWD" "${1#./}" "${1##*.}" $(stat -c %s "$1")
' _ {} \; > to_single_file
αғsнιη
źródło
2
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.

Benubird
źródło
„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
muru,
Oto dowód znaczenia znaków cudzysłowu: i.stack.imgur.com/oYSj2.png
pa4080 25.1017
1

find można to zrobić:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'

Sprawdź man findinne właściwości pliku.

Jeśli naprawdę potrzebujesz rozszerzenia, możesz dodać to:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
Katu
źródło