Làm cách nào để tôi làm việc đệ quy thông qua cây thư mục và thực thi một lệnh cụ thể trên mỗi tệp và xuất đường dẫn, tên tệp, phần mở rộng, kích thước tệp và một số văn bản cụ thể khác thành một tệp trong bash.
Làm cách nào để tôi làm việc đệ quy thông qua cây thư mục và thực thi một lệnh cụ thể trên mỗi tệp và xuất đường dẫn, tên tệp, phần mở rộng, kích thước tệp và một số văn bản cụ thể khác thành một tệp trong bash.
Câu trả lời:
Trong khi find
các giải pháp đơn giản và mạnh mẽ, tôi quyết định tạo ra một giải pháp phức tạp hơn, dựa trên chức năng thú vị này , mà tôi đã thấy vài ngày trước.
1. Tạo tệp tập lệnh thực thi, được gọi walk
, được đặt ở vị trí /usr/local/bin
có thể truy cập dưới dạng lệnh shell:
sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
nano
: Shift+ Insertđể dán; Ctrl+ Ovà Enterđể lưu; Ctrl+ Xđể thoát.2. Nội dung của kịch bản walk
là:
#!/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. Giải thích:
Cơ chế chính của walk()
chức năng được Zanna mô tả khá tốt trong câu trả lời của cô . Vì vậy, tôi sẽ chỉ mô tả phần mới.
Trong walk()
chức năng tôi đã thêm vòng lặp này:
for entry in "$1"/*; do [[ -f "$entry" ]] && file_specification; done
Điều đó có nghĩa là đối với mỗi $entry
tệp là một tệp sẽ được thực thi chức năng file_specification()
.
Hàm file_specification()
có hai phần. Phần đầu tiên lấy dữ liệu liên quan đến tệp - tên, đường dẫn, kích thước, v.v ... Phần thứ hai xuất dữ liệu ở dạng được định dạng tốt. Để định dạng dữ liệu được sử dụng lệnh printf
. Và nếu bạn muốn điều chỉnh tập lệnh, bạn nên đọc về lệnh này - ví dụ bài viết này .
Hàm file_specification()
này là nơi tốt để bạn có thể đặt lệnh cụ thể sẽ được thực thi cho mỗi tệp . Sử dụng định dạng này:
lệnh "$ {mục}"
Hoặc bạn có thể lưu đầu ra của lệnh dưới dạng biến, và sau đó printf
biến này, v.v.:
MY_VAR = "$ ( lệnh " $ {mục} ")" printf "% * s \ tFile size: \ t $ {YEL}% s $ {NCL} \ n" $ ((thụt + 4)) '' "$ MY_VAR"
Hoặc trực tiếp printf
đầu ra của lệnh:
printf "% * s \ tFile size: \ t $ {YEL}% s $ {NCL} \ n" $ ((thụt + 4)) '' "$ ( lệnh " $ {entry} ")"
Phần để cầu xin, được gọi Colourise the output
, khởi tạo một vài biến được sử dụng trong printf
lệnh để tô màu đầu ra. Thông tin thêm về điều này bạn có thể tìm thấy ở đây .
Ở dưới cùng của tập lệnh được thêm điều kiện bổ sung liên quan đến các đường dẫn tuyệt đối và tương đối.
4. Ví dụ về cách sử dụng:
Để chạy walk
cho thư mục hiện tại:
walk # You shouldn't use any argument,
walk ./ # but you can use also this format
Để chạy walk
cho bất kỳ thư mục con:
walk <directory name>
walk ./<directory name>
walk <directory name>/<sub directory>
Để chạy walk
cho bất kỳ thư mục khác:
walk /full/path/to/<directory name>
Để tạo một tệp văn bản, dựa trên walk
đầu ra:
walk > output.file
Để tạo tập tin đầu ra không có mã màu ( nguồn ):
walk | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > output.file
5. Trình diễn cách sử dụng:
Tôi hơi bối rối về lý do tại sao không ai đăng nó, nhưng thực sự bash
có khả năng đệ quy, nếu bạn bật globstar
tùy chọn và sử dụng toàn **
cầu. Như vậy, bạn có thể viết (gần như) bash
tập lệnh thuần túy sử dụng sao đó đệ quy như thế này:
#!/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
Lưu ý rằng ở đây chúng tôi sử dụng mở rộng tham số để có được các phần của tên tệp mà chúng tôi muốn và chúng tôi không dựa vào các lệnh bên ngoài ngoại trừ việc lấy kích thước tệp với du
và làm sạch đầu ra với awk
.
Và khi nó đi qua cây thư mục của bạn, đầu ra của bạn sẽ giống như thế này:
Path: ./glibc/glibc-2.23/benchtests
Filename: sprintf-source.c
Extension: c
Filesize: 326
Các quy tắc tiêu chuẩn của việc sử dụng tập lệnh được áp dụng: đảm bảo rằng nó có thể thực thi được chmod +x ./myscript.sh
và chạy nó từ thư mục hiện tại thông qua ./myscript.sh
hoặc đặt nó vào ~/bin
và chạy source ~/.profile
.
"$(file "$i")"
(trong đoạn script trên là phần thứ hai của printf) sẽ trả về?
output the path, filename, extension, filesize
, vì vậy câu trả lời phù hợp với những gì được hỏi. :)
Bạn có thể sử dụng find
để thực hiện công việc
find /path/ -type f -exec ls -alh {} \;
Điều này sẽ giúp bạn nếu bạn chỉ muốn liệt kê tất cả các tệp có kích thước.
-exec
sẽ cho phép bạn thực thi lệnh hoặc tập lệnh tùy chỉnh cho từng tệp
\;
được sử dụng để phân tích từng tệp một, bạn có thể sử dụng +;
nếu bạn muốn ghép chúng (có nghĩa là tên tệp).
Chỉ với find
.
find /path/ -type f -printf "path:%h fileName:%f size:%kKB Some Text\n" > to_single_file
Hoặc, bạn có thể sử dụng bên dưới thay thế:
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
Nếu bạn biết cây sâu bao nhiêu thì cách dễ nhất là sử dụng ký tự đại diện *
.
Viết tất cả mọi thứ bạn muốn làm như một kịch bản shell hoặc hàm
function thing() { ... }
sau đó chạy for i in *; do thing "$i"; done
, for i in */*; do thing "$i"; done
... vv
Trong chức năng / tập lệnh của bạn, bạn có thể sử dụng một số thử nghiệm đơn giản để chọn ra các tệp bạn muốn làm việc và làm bất cứ điều gì bạn cần với chúng.
$i
.
for i in */*
hoạt động. Tại đây, hãy kiểm tra nó:for i in */*; do printf "|%s|\n" "$i"; done
find
có thể làm điều này:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\n'
Có một cái nhìn man find
cho các thuộc tính tập tin khác.
Nếu bạn thực sự cần tiện ích mở rộng, bạn có thể thêm phần này:
find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;