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.
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.
Jawaban:
Sementara find
solusi 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.
1. Buat file skrip yang dapat dieksekusi, disebut walk
, yang terletak di /usr/local/bin
agar dapat diakses sebagai perintah shell:
sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
nano
: Shift+ Insertuntuk menempel; Ctrl+ Odan Enteruntuk menghemat; Ctrl+ Xuntuk keluar.2. Isi skrip walk
adalah:
#!/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 $entry
file 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 printf
variabel ini, dll .:
MY_VAR = "$ ( perintah " $ {entry} ")" printf "% * s \ tFile ukuran: \ t $ {YEL}% s $ {NCL} \ n" $ ((indent + 4)) '' "$ MY_VAR"
Atau langsung printf
output 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 walk
direktori saat ini:
walk # You shouldn't use any argument,
walk ./ # but you can use also this format
Untuk menjalankan walk
direktori anak:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
Untuk menjalankan walk
direktori lain:
walk /full/path/to/<directory name>
Untuk membuat file teks, berdasarkan walk
output:
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:
Saya sedikit bingung mengapa belum ada yang mempostingnya, tetapi memang bash
memiliki kemampuan rekursif, jika Anda mengaktifkan globstar
opsi 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 du
dan 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.sh
dan jalankan dari direktori saat ini melalui ./myscript.sh
atau letakkan ~/bin
dan jalankan source ~/.profile
.
"$(file "$i")"
(dalam skrip di atas sebagai bagian kedua dari printf) akan kembali?
output the path, filename, extension, filesize
, sehingga jawabannya cocok dengan yang ditanyakan. :)
Anda dapat menggunakannya find
untuk melakukan pekerjaan itu
find /path/ -type f -exec ls -alh {} \;
Ini akan membantu Anda jika Anda hanya ingin membuat daftar semua file dengan ukuran.
-exec
akan 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).
Dengan find
hanya.
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
find -printf
). +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.
$i
.
for i in */*
kerjanya. Ini, ujilah:for i in */*; do printf "|%s|\n" "$i"; done
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"' {} \;