Skrip bash rekursif untuk mengumpulkan informasi tentang setiap file dalam struktur direktori

14

Bagaimana cara saya bekerja secara rekursif melalui pohon direktori dan menjalankan perintah khusus pada setiap file, dan menampilkan path, nama file, ekstensi, filesize dan beberapa teks spesifik lainnya ke satu file di bash.

SPooKYiNeSS
sumber
lol, terima kasih untuk hasil editnya; Aku akan menjadi orang pertama yang mengakui bahwa aku terlalu rumit, karena aku terbiasa ditanya 800 pertanyaan yang tidak relevan di dunia hooman; jadi saya mencoba menjawab yang sudah jelas dalam pertanyaan; saya akan belajar :-)
SPooKYiNeSS
1
OK, saya pikir pertanyaannya cukup jelas tentang apa yang harus dilakukan, melalui pohon direktori dan info keluaran tentang setiap file. Pertanyaannya cukup jelas, dan jika dilihat dari jumlah jawaban, orang memahaminya dengan cukup baik. 3 suara untuk menjadi tidak jelas benar-benar tidak layak untuk pertanyaan ini
Sergiy Kolodyazhnyy

Jawaban:

15

Sementara findsolusi sederhana dan kuat, saya memutuskan untuk membuat solusi yang lebih rumit, yang didasarkan pada fungsi yang menarik ini , yang saya lihat beberapa hari yang lalu.

  • Penjelasan lebih lanjut dan dua skrip lainnya, berdasarkan arus disediakan di sini .

1. Buat file skrip yang dapat dieksekusi, disebut walk, yang terletak di /usr/local/binagar dapat diakses sebagai perintah shell:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • Salin konten skrip di bawah ini dan gunakan di nano: Shift+ Insertuntuk menempel; Ctrl+ Odan Enteruntuk menghemat; Ctrl+ Xuntuk keluar.

2. Isi skrip walkadalah:

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

  • Mekanisme utama walk()fungsi ini dijelaskan dengan sangat baik oleh Zanna dalam jawabannya . Jadi saya hanya akan menjelaskan bagian yang baru.

  • Dalam walk()fungsi saya telah menambahkan loop ini:

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

    Itu berarti untuk setiap $entryfile yang akan dieksekusi fungsi file_specification().

  • Fungsi ini file_specification()memiliki dua bagian. Bagian pertama mendapatkan data yang terkait dengan file - nama, jalur, ukuran, dll. Bagian kedua menampilkan data dalam bentuk yang diformat dengan baik. Untuk memformat data digunakan perintah printf. Dan jika Anda ingin mengubah skrip Anda harus membaca tentang perintah ini - misalnya artikel ini .

  • Fungsi file_specification()adalah tempat yang baik di mana Anda dapat menempatkan perintah khusus yang harus dijalankan untuk setiap file . Gunakan format ini:

    perintah "$ {entri}"

    Atau Anda dapat menyimpan output dari perintah sebagai variabel, dan kemudian printfvariabel ini, dll .:

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

    Atau langsung printfoutput dari perintah:

    printf "% * s \ tFile ukuran: \ t $ {YEL}% s $ {NCL} \ n" $ ((indent + 4)) '' "$ ( perintah " $ {entry} ")"

  • Bagian ke meminta, disebut Colourise the output, menginisialisasi beberapa variabel yang digunakan dalamprintf perintah untuk mewarnai output. Lebih lanjut tentang ini dapat Anda temukan di sini .

  • Ke bagian bawah skrip ditambahkan kondisi tambahan yang berhubungan dengan jalur absolut dan relatif.

4. Contoh penggunaan:

  • Untuk menjalankan walkdirektori saat ini:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Untuk menjalankan walkdirektori anak:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Untuk menjalankan walkdirektori lain:

    walk /full/path/to/<directory name>
  • Untuk membuat file teks, berdasarkan walkoutput:

    walk > output.file
  • Untuk membuat file keluaran tanpa kode warna ( sumber ):

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

5. Demonstrasi penggunaan:

masukkan deskripsi gambar di sini

pa4080
sumber
Itu banyak pekerjaan, tetapi memang terlihat bagus. Kerja bagus !
Sergiy Kolodyazhnyy
Proses apa yang Anda gunakan untuk membuat gifs @ pa4080?
pbhj
@ pbhj, di bawah Ubuntu saya menggunakan Peek itu sederhana dan bagus, tetapi kadang-kadang macet dan tidak memiliki kemampuan mengedit. Sebagian besar GIF saya dibuat di bawah Windows, tempat saya merekam jendela koneksi VNC. Saya memiliki mesin desktop terpisah yang terutama saya gunakan untuk pembuatan MS Office dan GIF :) Alat yang saya gunakan adalah ScreenToGif . Ini adalah opensource, gratis, dan memiliki editor yang kuat dan pemrosesan mekanisum. Sayangnya saya tidak dapat menemukan alat seperti ScreenToGif untuk Ubuntu.
pa4080
13

Saya sedikit bingung mengapa belum ada yang mempostingnya, tetapi memang bashmemiliki kemampuan rekursif, jika Anda mengaktifkan globstaropsi dan menggunakan **glob. Dengan demikian, Anda dapat menulis (hampir) bash skrip murni yang menggunakan globstar rekursif seperti ini:

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

Perhatikan bahwa di sini kita menggunakan ekspansi parameter untuk mendapatkan bagian dari nama file yang kita inginkan dan kita tidak bergantung pada perintah eksternal kecuali untuk mendapatkan ukuran file dengan dudan membersihkan keluaran awk.

Dan saat melintasi pohon direktori Anda, output Anda akan menjadi seperti ini:

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

Aturan standar penggunaan skrip berlaku: pastikan itu dapat dieksekusi dengan chmod +x ./myscript.shdan jalankan dari direktori saat ini melalui ./myscript.shatau letakkan ~/bindan jalankan source ~/.profile.

Sergiy Kolodyazhnyy
sumber
Jika Anda mencetak nama file lengkap, apa yang diberikan "ekstensi"? Mungkin Anda benar-benar menginginkan informasi MIME yang "$(file "$i")"(dalam skrip di atas sebagai bagian kedua dari printf) akan kembali?
pbhj
1
@ pbhj Bagi saya pribadi? Tidak ada. Namun OP yang menanyakan pertanyaan itu ditanyakan output the path, filename, extension, filesize , sehingga jawabannya cocok dengan yang ditanyakan. :)
Sergiy Kolodyazhnyy
12

Anda dapat menggunakannya finduntuk melakukan pekerjaan itu

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

Ini akan membantu Anda jika Anda hanya ingin membuat daftar semua file dengan ukuran.

-execakan memungkinkan Anda untuk menjalankan perintah atau skrip khusus untuk setiap file yang \;digunakan untuk mem-parsing file satu per satu, Anda dapat menggunakan +;jika Anda ingin menggabungkannya (berarti nama file).

Rajesh Rajendran
sumber
Ini bagus, tetapi tidak menjawab semua persyaratan yang disebutkan OP.
αғsнιη
1
@ αғsнιη Saya baru saja memberinya template untuk dikerjakan. Saya tahu, ini bukan jawaban yang lengkap untuk pertanyaan ini, karena saya pikir pertanyaan itu sendiri memiliki cakupan yang luas.
Rajesh Rajendran
6

Dengan findhanya.

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

Atau, Anda bisa menggunakan di bawah ini sebagai gantinya:

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нιη
sumber
2
Elegan dan sederhana (jika Anda tahu find -printf). +1
David Foerster
1

Jika Anda tahu seberapa dalam pohon itu, cara termudah adalah menggunakan wildcard * .

Tulis semua yang ingin Anda lakukan sebagai skrip shell atau fungsi

function thing() { ... }

kemudian jalankan for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done, ... dll

Dalam fungsi / skrip Anda, Anda dapat menggunakan beberapa tes sederhana untuk memilih file yang ingin Anda kerjakan dan melakukan apa pun yang Anda perlukan dengannya.

Benubird
sumber
"Ini tidak akan berfungsi jika ada nama file Anda yang memiliki ruang di dalamnya" ... karena Anda lupa mengutip variabel Anda! Gunakan "$ i" sebagai ganti $i.
muru
@muru tidak, alasan itu tidak berhasil adalah karena loop "untuk" terpecah pada spasi - " / 'diperluas menjadi daftar semua file yang dipisahkan oleh ruang. Anda dapat mengatasinya, misalnya dengan mengotak-atik IFS, tapi pada saat itu Anda mungkin juga hanya menggunakan find.
Benubird
@ pa4080 tidak relevan dengan jawaban ini, tapi itu tetap terlihat sangat berguna, terima kasih!
Benubird
Saya pikir Anda tidak mengerti cara for i in */*kerjanya. Ini, ujilah:for i in */*; do printf "|%s|\n" "$i"; done
muru
Berikut adalah bukti penting dari tanda kutip: i.stack.imgur.com/oYSj2.png
pa4080
1

find dapat melakukan ini:

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

Lihatlah man find untuk properti file lainnya.

Jika Anda benar-benar membutuhkan ekstensi, Anda dapat menambahkan ini:

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