Tập lệnh bash đệ quy để thu thập thông tin về mỗi tệp trong cấu trúc thư mục


14

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.


lol, cảm ơn vì đã chỉnh sửa; Tôi sẽ là người đầu tiên thừa nhận tôi quá phức tạp, bởi vì tôi đã từng được hỏi 800 câu hỏi không liên quan trong thế giới hooman; Vì vậy, tôi cố gắng trả lời những câu hỏi rõ ràng trong các câu hỏi; Tôi sẽ học mặc dù :-)
SPooKYiNeSS

1
OK, tôi nghĩ rằng câu hỏi khá rõ ràng về những gì nên được thực hiện, đi qua cây thư mục và thông tin đầu ra về mỗi tệp. Câu hỏi khá rõ ràng và đánh giá theo số lượng câu trả lời, mọi người hiểu nó khá rõ. 3 phiếu bầu không rõ ràng thực sự không xứng đáng với câu hỏi này
Sergiy Kolodyazhnyy

Câu trả lời:


15

Trong khi findcá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.

  • Giải thích thêm và hai kịch bản khác, dựa trên hiện tại được cung cấp ở đây .

1. Tạo tệp tập lệnh thực thi, được gọi walk, được đặt ở vị trí /usr/local/bincó 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
  • Sao chép nội dung tập lệnh bên dưới và sử dụng trong nano: Shift+ Insertđể dán; Ctrl+ OEnterđể lưu; Ctrl+ Xđể thoát.

2. Nội dung của kịch bản walklà:

#!/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 $entrytệ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 đó printfbiế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 printflệ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 walkcho thư mục hiện tại:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • Để chạy walkcho bất kỳ thư mục con:

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • Để chạy walkcho 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:

nhập mô tả hình ảnh ở đây


Đó là toàn bộ công việc, nhưng có vẻ tốt. Làm tốt lắm !
Sergiy Kolodyazhnyy

Quá trình bạn đang sử dụng để tạo những gifs @ pa4080 là gì?
pbhj

@pbhj, trong Ubuntu Tôi đang sử dụng Peek, nó đơn giản và đẹp, nhưng đôi khi gặp sự cố và không có khả năng chỉnh sửa. Hầu hết các GIF của tôi được tạo trong Windows, nơi tôi đang ghi lại cửa sổ kết nối VNC. Tôi có một máy tính để bàn riêng mà chủ yếu tôi đang sử dụng để tạo MS Office và GIF :) Công cụ mà tôi đang sử dụng là ScreenToGif . Nó là mã nguồn mở, miễn phí và có trình soạn thảo và cơ chế xử lý mạnh mẽ. Thật không may, tôi không thể tìm thấy công cụ như ScreenToGif cho Ubuntu.
pa4080

13

Tôi hơi bối rối về lý do tại sao không ai đăng nó, nhưng thực sự bashcó khả năng đệ quy, nếu bạn bật globstartù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 duvà 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.shvà chạy nó từ thư mục hiện tại thông qua ./myscript.shhoặc đặt nó vào ~/binvà chạy source ~/.profile.


Nếu bạn đang in tên tệp đầy đủ, "phần mở rộng" sẽ cung cấp cho bạn thêm gì? Có lẽ bạn thực sự muốn thông tin MIME mà "$(file "$i")"(trong đoạn script trên là phần thứ hai của printf) sẽ trả về?
pbhj

1
@pbhj Cá nhân tôi? Không có gì. Nhưng OP, người đã hỏi câu hỏi 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. :)
Sergiy Kolodyazhnyy

12

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.

-execsẽ 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).


Điều này là tốt, nhưng không trả lời cho tất cả các yêu cầu OP đã đề cập.
αsнι

1
@ αғsнιη Tôi chỉ cho anh ta một mẫu để làm việc. Tôi biết, đây không phải là một câu trả lời hoàn chỉnh cho câu hỏi này, vì tôi nghĩ rằng bản thân câu hỏi có phạm vi rộng.
Rajesh Rajendran

6

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

2
Thanh lịch và đơn giản (nếu bạn biết về find -printf). +1
David Foerster

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ều này sẽ không hoạt động nếu bất kỳ tên tệp nào của bạn có khoảng trắng trong đó" ... vì bạn quên trích dẫn các biến của mình! Sử dụng "$ i" thay vì $i.
muru

@muru không, lý do nó không hoạt động là do vòng lặp "for" phân tách trên khoảng trắng - " / 'được mở rộng thành một danh sách được phân tách bằng dấu cách của tất cả các tệp. Bạn có thể giải quyết vấn đề này, ví dụ bằng cách làm rối IFS, nhưng tại thời điểm đó, bạn có thể chỉ cần sử dụng find.
Benubird

@ pa4080 không liên quan đến câu trả lời này, nhưng dù sao thì nó cũng có vẻ siêu hữu ích, cảm ơn!
Benubird

Tôi nghĩ bạn không hiểu làm thế nào for i in */*hoạt động. Tại đây, hãy kiểm tra nó:for i in */*; do printf "|%s|\n" "$i"; done
muru

Dưới đây là bằng chứng về tầm quan trọng của dấu ngoặc kép: i.stack.imgur.com/oYSj2.png
pa4080

1

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 findcho 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"' {} \;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.