Script bash recursivo para coletar informações sobre cada arquivo em uma estrutura de diretórios

14

Como trabalho recursivamente através de uma árvore de diretórios e executo um comando específico em cada arquivo, e envia o caminho, nome do arquivo, extensão, tamanho do arquivo e algum outro texto específico para um único arquivo no bash.

SPooKYiNeSS
fonte
lol, obrigado pela edição; serei o primeiro a admitir que complico demais as coisas, porque estou acostumado a receber 800 perguntas irrelevantes no mundo masculino; então eu tento responder as óbvias nas perguntas; eu vou aprender :-)
SPooKYiNeSS
1
OK, acho que a pergunta é bem clara sobre o que deve ser feito, percorrer a árvore de diretórios e gerar informações sobre cada arquivo. A pergunta é bastante clara e, a julgar pela quantidade de respostas, as pessoas a entendem bastante bem. Os 3 votos por não serem realmente claros não são merecidos para esta pergunta #
Sergiy Kolodyazhnyy 25/10

Respostas:

15

Embora as findsoluções sejam simples e poderosas, decidi criar uma solução mais complicada, baseada nessa interessante função que vi alguns dias atrás.

  • Mais explicações e dois outros scripts, com base na corrente, são fornecidos aqui .

1. Crie um arquivo de script executável, chamado walk, localizado /usr/local/binpara ser acessível como comando shell:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Copie o conteúdo do script abaixo e use nano: Shift+ Insertpara colar; Ctrl+ Oe Enterpara salvar; Ctrl+ Xpara sair.

2. O conteúdo do 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. Explicação:

  • O principal mecanismo da walk()função é muito bem descrito por Zanna em sua resposta . Então, vou descrever apenas a nova parte.

  • Dentro da walk()função eu adicionei este loop:

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

    Isso significa que para cada $entryarquivo que será executado, a função será executada file_specification().

  • A função file_specification()tem duas partes. A primeira parte obtém dados relacionados ao arquivo - nome, caminho, tamanho etc. A segunda parte gera os dados em uma forma bem formatada. Para formatar os dados é usado o comando printf. E se você deseja ajustar o script, leia sobre este comando - por exemplo, este artigo .

  • A função file_specification()é um bom local para colocar o comando específico que deve ser executado para cada arquivo . Use este formato:

    comando "$ {entry}"

    Ou você pode salvar a saída do comando como variável e, em seguida, printfesta variável, etc .:

    MY_VAR = "$ ( comando " $ {entry} ")"
    printf "% * s \ tTamanho do arquivo: \ t $ {YEL}% s $ {NCL} \ n" $ ((recuo + 4)) '' "$ MY_VAR"

    Ou diretamente printfa saída do comando:

    printf "% * s \ tTamanho do arquivo: \ t $ {YEL}% s $ {NCL} \ n" $ ((recuo + 4)) '' "$ ( comando " $ {entry} ")"

  • A seção ao pedido, chamada Colourise the output, inicializa algumas variáveis ​​que são usadas no printfcomando para colorir a saída. Mais sobre isso você pode encontrar aqui .

  • Na parte inferior do script, é adicionada uma condição adicional que lida com caminhos absolutos e relativos.

4. Exemplos de uso:

  • Para executar walkno diretório atual:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Para executar walkem qualquer diretório filho:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Para executar walkem qualquer outro diretório:

    walk /full/path/to/<directory name>
  • Para criar um arquivo de texto, com base na walksaída:

    walk > output.file
  • Para criar um arquivo de saída sem códigos de cores ( origem ):

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

5. Demonstração de uso:

insira a descrição da imagem aqui

pa4080
fonte
Isso dá muito trabalho, mas parece bom. Bom trabalho !
Sergiy Kolodyazhnyy 25/10
Que processo você está usando para criar esses gifs @ pa4080?
Pbhj
@pbhj, no Ubuntu, estou usando o Peek , é simples e agradável, mas às vezes trava e não tem capacidade de edição. A maioria dos meus GIFs é criada no Windows, onde estou gravando a janela da conexão VNC. Eu tenho uma máquina de desktop separada que principalmente estou usando para criação do MS Office e GIF :) A ferramenta que estou usando lá é o ScreenToGif . É de código aberto, gratuito e possui poderoso editor e mecanismo de processamento. Infelizmente não consigo encontrar ferramentas como o ScreenToGif para Ubuntu.
pa4080
13

Estou um pouco perplexo com o motivo de ninguém ter postado ainda, mas, de fato bash, possui recursos recursivos, se você ativar a globstaropção e usar o **glob. Como tal, você pode escrever (quase) um bash script puro que usa a globstar recursiva como esta:

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

Observe que aqui usamos a expansão de parâmetros para obter as partes do nome do arquivo que queremos e não confiamos em comandos externos, exceto para obter o tamanho do arquivo due limpar a saída com awk.

E como ele atravessa sua árvore de diretórios, sua saída deve algo como isto:

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

Aplicam-se regras padrão de uso de script: verifique se ele é executável chmod +x ./myscript.she execute-o no diretório atual via ./myscript.shou coloque-o ~/bine execute-o source ~/.profile.

Sergiy Kolodyazhnyy
fonte
Se você estiver imprimindo o nome completo, o que extra "extensão" oferece? Talvez você realmente queira as informações MIME que "$(file "$i")"(no script acima como segunda parte de um printf) retornariam?
pbhj
1
@pbhj Para mim, pessoalmente? Nada. Mas o OP que fez a pergunta solicitou output the path, filename, extension, filesize , portanto a resposta corresponde ao que é solicitado. :)
Sergiy Kolodyazhnyy
12

Você pode usar findpara fazer o trabalho

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

Isso o ajudará se você quiser apenas listar todos os arquivos com tamanho.

-execpermitirá que você execute comandos ou scripts personalizados para cada arquivo \;usado para analisar arquivos um por um, você pode usar +;se quiser concatená-los (significa nomes de arquivos).

Rajesh Rajendran
fonte
Isso é bom, mas não responde a todos os requisitos de OP mencionados.
αғsнιη
1
@ αғsнιη Acabei de lhe dar um modelo para trabalhar. Eu sei, essa não é uma resposta completa para essa pergunta, pois acho que a pergunta em si é ampla em seu escopo.
Rajesh Rajendran
6

Com findapenas.

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

Ou então, você pode usar abaixo:

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нιη
fonte
2
Elegante e simples (se você conhece find -printf). 1
David Foerster
1

Se você souber a profundidade da árvore, a maneira mais fácil será usar o curinga *.

Escreva tudo o que você deseja fazer como um shell script ou uma função

function thing() { ... }

em seguida, executar for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... etc

Dentro da sua função / script, você pode usar alguns testes simples para destacar os arquivos com os quais deseja trabalhar e fazer o que for necessário com eles.

Benubird
fonte
"isso não funcionará se algum dos seus nomes de arquivos tiver espaços" ... porque você esqueceu de citar suas variáveis! Use "$ i" em vez de $i.
Muru
@muru não, a razão pela qual ele não funciona é porque o loop "for" se divide em espaços - " / 'é expandido em uma lista separada por espaços de todos os arquivos. Você pode contornar isso, por exemplo, mexendo com o IFS, mas nesse ponto você também pode usar find
#
@ pa4080 não é relevante para esta resposta, mas parece super útil de qualquer maneira, obrigado!
Benubird
Eu acho que você não entende como for i in */*funciona. Aqui, teste-o:for i in */*; do printf "|%s|\n" "$i"; done
muru
Aqui está uma evidência da importância das aspas: i.stack.imgur.com/oYSj2.png
pa4080 25/17
1

find posso fazer isso:

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

Dê uma olhada em man findoutras propriedades do arquivo.

Se você realmente precisa da extensão, pode adicionar este:

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