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:

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


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.


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).


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

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.


„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"' {} \;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.