Script bash ricorsivo per raccogliere informazioni su ciascun file in una struttura di directory


14

Come posso lavorare in modo ricorsivo attraverso un albero di directory ed eseguire un comando specifico su ciascun file e generare il percorso, il nome file, l'estensione, la dimensione del file e alcuni altri testi specifici su un singolo file in bash.


lol, grazie per la modifica; sarò il primo ad ammettere di complicare troppo le cose, perché sono abituato a farmi 800 domande irrilevanti nel mondo degli hooman; quindi cerco di rispondere a quelli ovvi nelle domande; imparerò comunque :-)
SPooKYiNeSS il

1
OK, penso che la domanda sia abbastanza chiara su cosa dovrebbe essere fatto, passare attraverso l'albero delle directory e ottenere informazioni su ogni file. La domanda è piuttosto chiara e, a giudicare dalla quantità di risposte già, la gente la capisce abbastanza bene. I 3 voti per essere poco chiari non sono davvero meritati per questa domanda
Sergiy Kolodyazhnyy,

Risposte:


15

Mentre le findsoluzioni sono semplici e potenti, ho deciso di creare una soluzione più complicata, che si basa su questa interessante funzione , che ho visto pochi giorni fa.

  • Altre spiegazioni e altri due script, basati sull'attuale, sono forniti qui .

1. Creare il file di script eseguibile, chiamato walk, che si trova in /usr/local/binessere accessibile come comando shell:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Copia il contenuto dello script seguente e utilizzalo in nano: Shift+ Insertper incollare; Ctrl+ Oe Enterper salvare; Ctrl+ Xper uscita.

2. Il contenuto dello script walkè:

#!/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. Spiegazione:

  • Il principale meccanismo della walk()funzione è abbastanza ben descritto da Zanna nella sua risposta . Quindi descriverò solo la nuova parte.

  • All'interno della walk()funzione ho aggiunto questo loop:

    for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done

    Ciò significa che per ognuno $entryche è un file verrà eseguita la funzione file_specification().

  • La funzione file_specification()ha due parti. La prima parte ottiene i dati relativi al file: nome, percorso, dimensione, ecc. La seconda parte restituisce i dati in forma ben formattata. Per formattare i dati viene utilizzato il comando printf. E se vuoi modificare lo script, dovresti leggere questo comando, ad esempio questo articolo .

  • La funzione file_specification()è un buon posto dove puoi inserire il comando specifico che dovrebbe essere eseguito per ogni file . Usa questo formato:

    comando "$ {entry}"

    Oppure puoi salvare l'output del comando come variabile, quindi printfquesta variabile, ecc .:

    MY_VAR = "$ ( comando " $ {entry} ")"
    printf "% * s \ tDimensione file: \ t $ {YEL}% s $ {NCL} \ n" $ ((indent + 4)) '' "$ MY_VAR"

    O direttamente printfl'output del comando:

    printf "% * s \ tDimensione file: \ t $ {YEL}% s $ {NCL} \ n" $ ((trattino + 4)) '' "$ ( comando " $ {entry} ")"

  • La sezione dell'accattonaggio, chiamata Colourise the output, inizializza alcune variabili che vengono utilizzate all'interno del printfcomando per colorare l'output. Maggiori informazioni su questo puoi trovare qui .

  • Nella parte inferiore dello script viene aggiunta una condizione aggiuntiva che si occupa di percorsi assoluti e relativi.

4. Esempi di utilizzo:

  • Per eseguire walkla directory corrente:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Per eseguire walkqualsiasi directory figlio:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Per eseguire walkper qualsiasi altra directory:

    walk /full/path/to/<directory name>
  • Per creare un file di testo, in base walkall'output:

    walk > output.file
  • Per creare un file di output senza codici colore ( sorgente ):

    walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file

5. Dimostrazione di utilizzo:

inserisci qui la descrizione dell'immagine


È un sacco di lavoro, ma sembra buono. Buon lavoro !
Sergiy Kolodyazhnyy,

Quale processo stai usando per realizzare quelle gif @ pa4080?
pbhj,

@pbhj, con Ubuntu sto usando Peek è semplice e carino, ma a volte si blocca e non ha capacità di modifica. La maggior parte delle mie GIF sono create sotto Windows, dove sto registrando la finestra della connessione VNC. Ho una macchina desktop separata che principalmente sto usando per la creazione di MS Office e GIF :) Lo strumento che sto usando è ScreenToGif . È open source, gratuito e ha un potente editor e un meccanismo di elaborazione. Purtroppo non riesco a trovare uno strumento come ScreenToGif per Ubuntu.
pa4080,

13

Sono leggermente perplesso sul perché nessuno l'abbia ancora pubblicato, ma in effetti bashha capacità ricorsive, se abiliti l' globstaropzione e usi **glob. Come tale, puoi scrivere uno bash script (quasi) puro che utilizza quel globstar ricorsivo in questo modo:

#!/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

Nota che qui usiamo l'espansione dei parametri per ottenere le parti del nome file che vogliamo e non ci affidiamo a comandi esterni, tranne per ottenere le dimensioni del file due pulire l'output con awk.

E mentre attraversa l'albero delle directory, l'output dovrebbe essere simile a questo:

Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326

Si applicano le regole standard di utilizzo degli script: assicurarsi che sia eseguibile chmod +x ./myscript.shed eseguirlo dalla directory corrente tramite ./myscript.sho posizionarlo ~/bined eseguirlo source ~/.profile.


Se stai stampando il nome file completo che cosa ti dà "extension"? Forse vuoi davvero le informazioni MIME che "$(file "$i")"(nello script sopra come seconda parte di un printf) restituirebbero?
pbhj,

1
@pbhj A me personalmente? Niente. Ma OP che ha posto la domanda output the path, filename, extension, filesize , quindi la risposta corrisponde a ciò che viene chiesto. :)
Sergiy Kolodyazhnyy

12

Puoi usare findper fare il lavoro

find /path/ -type f -exec ls -alh {} \;

Questo ti aiuterà se vuoi solo elencare tutti i file con dimensioni.

-execti permetterà di eseguire comandi o script personalizzati per ogni file \;utilizzato per analizzare i file uno per uno, puoi usarli +;se vuoi concatenarli (significa nomi di file).


Questo è carino, ma non risponde a tutti i requisiti OP menzionati.
αғsнιη,

1
@ αғsнιη Gli ho appena dato un modello su cui lavorare. So che questa non è una risposta completa a questa domanda, poiché ritengo che la domanda stessa abbia una portata ampia.
Rajesh Rajendran,

6

Con findsolo.

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

In alternativa, è possibile utilizzare invece di seguito:

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
Elegante e semplice (se conosci find -printf). +1
David Foerster,

1

Se sai quanto è profondo l'albero, il modo più semplice sarà usare il carattere jolly *.

Scrivi tutto quello che vuoi fare come uno script di shell o una funzione

function thing() { ... }

quindi eseguire for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done, ecc ...

All'interno della tua funzione / script, puoi usare alcuni semplici test per individuare i file con cui vuoi lavorare e fare tutto ciò che ti serve.


"questo non funzionerà se uno dei tuoi nomi di file contiene degli spazi" ... perché ti sei dimenticato di citare le tue variabili! Usa "$ i" invece di $i.
muru,

@muru no, il motivo per cui non funziona è perché il ciclo "for" si divide su spazi - " / 'viene espanso in un elenco separato da spazi di tutti i file. Puoi aggirare questo problema, ad esempio facendo un pasticcio con l'IFS, ma a quel punto potresti anche usare find.
Benubird,

@ pa4080 non è rilevante per questa risposta, ma sembra comunque super utile, grazie!
Benubird,

Penso che tu non capisca come for i in */*funziona. Qui, for i in */*; do printf "|%s|\n" "$i"; done
provalo

Ecco una prova dell'importanza delle virgolette: i.stack.imgur.com/oYSj2.png
pa4080

1

find puoi fare questo:

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

Dai un'occhiata ad man findaltre proprietà del file.

Se hai davvero bisogno dell'estensione, puoi aggiungere questo:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.