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

11

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

    
por SPooKYiNeSS 25.10.2017 / 06:49

6 respostas

11

Estou um pouco perplexo a respeito de porque ninguém postou ainda, mas de fato bash tem recursos recursivos, se você habilitar a opção globstar e usar ** glob. Como tal, você pode escrever (quase) puro bash script que usa esse globstar recursivo assim:

#!/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 }')"
        # some other command can go here
        printf "\n\n"
    fi
done

Observe que aqui usamos a expansão de parâmetro para obter as partes do nome do arquivo desejadas e não estamos contando com comandos externos, exceto para obter o tamanho do arquivo com du e limpar a saída com awk .

E ao atravessar sua árvore de diretórios, sua saída deve ser algo assim:

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

Regras padrão de uso de script se aplicam: verifique se ele é executável com chmod +x ./myscript.sh e execute-o a partir do diretório atual via ./myscript.sh ou coloque-o em ~/bin e execute source ~/.profile .

    
por Sergiy Kolodyazhnyy 25.10.2017 / 11:43
13

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

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

1. Crie um arquivo de script executável, chamado walk , localizado em /usr/local/bin para 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 em nano : Deslocar + Inserir para colar; Ctrl + O e Enter para salvar; Ctrl + X para sair.

2. O conteúdo do script walk é:

#!/bin/bash

# Colourise the output
RED='3[0;31m'        # Red
GRE='3[0;32m'        # Green
YEL='3[1;33m'        # Yellow
NCL='3[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" '' ""
        # If the entry is a file do some operations
        for entry in ""/*; do [[ -f "$entry" ]] && file_specification; done
        # If the entry is a directory call walk() == create recursion
        for entry in ""/*; do [[ -d "$entry" ]] && walk "$entry" $((indent+4)); done
}

# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()
[[ -z "" ]] && ABS_PATH="${PWD}" || cd "" && ABS_PATH="${PWD}"
walk "${ABS_PATH}"      
echo                    

3. Explicação:

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

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

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

    Isso significa que para cada $entry que é um arquivo será executada a função 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 formato bem formatado. Para formatar os dados é usado o comando printf . E se você quiser ajustar o script, leia este comando - por exemplo, este artigo .

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

    command "${entry}"

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

    MY_VAR="$(command "${entry}")"
    printf "%*s\tFile size:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$MY_VAR"

    Ou diretamente printf da saída do comando:

    printf "%*s\tFile size:\t${YEL}%s${NCL}\n" $((indent+4)) '' "$(command "${entry}")"
  • A seção para o início, chamada Colourise the output , inicializa algumas variáveis que são usadas dentro do comando printf 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 walk no diretório atual:

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

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

    walk /full/path/to/<directory name>
    
  • Para criar um arquivo de texto, com base no walk output:

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

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

5. Demonstração de uso:

    
por pa4080 25.10.2017 / 11:02
10

Você pode usar find para fazer o trabalho

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

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

-exec permitirá 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).

    
por Rajesh Rajendran 25.10.2017 / 07:11
6

com find apenas.

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

Ou 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 "")
' _ {} \; > to_single_file
    
por αғsнιη 25.10.2017 / 09:45
1

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

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

function thing() { ... }

execute for i in *; do thing "$i"; done , for i in */*; do thing "$i"; done , ... etc

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

    
por Benubird 25.10.2017 / 11:35
1

find pode fazer isso:

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

Dê uma olhada em man find para outras propriedades de arquivo.

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

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

Tags