สคริปต์ bash แบบเรียกซ้ำเพื่อรวบรวมข้อมูลเกี่ยวกับแต่ละไฟล์ในโครงสร้างไดเรกทอรี


14

ฉันจะทำงานซ้ำ ๆ ผ่านไดเรคทอรีต้นไม้และรันคำสั่งเฉพาะในแต่ละไฟล์และออกพา ธ , ชื่อไฟล์, นามสกุล, ขนาดไฟล์และข้อความเฉพาะอื่น ๆ ไปยังไฟล์เดียวในไฟล์ทุบตี


ขอบคุณสำหรับการแก้ไข; ฉันจะเป็นคนแรกที่ยอมรับว่าฉันทำสิ่งที่ซับซ้อนมากเกินไปเพราะฉันเคยถูกถามคำถามที่ไม่เกี่ยวข้อง 800 คำถามในโลก hooman; ดังนั้นฉันพยายามตอบคำถามที่ชัดเจนในคำถาม; ฉันจะเรียนรู้ว่า :-)
SPooKYiNeSS

1
ตกลงฉันคิดว่าคำถามนั้นค่อนข้างชัดเจนในสิ่งที่ควรทำผ่านแผนผังไดเรกทอรีและข้อมูลผลลัพธ์เกี่ยวกับแต่ละไฟล์ คำถามค่อนข้างชัดเจนและตัดสินจากจำนวนคำตอบที่ผู้คนเข้าใจดีพอสมควร 3 คะแนนสำหรับการมีความไม่ชัดเจนจริง ๆ ไม่สมควรได้รับคำถามนี้
Sergiy Kolodyazhnyy

คำตอบ:


16

ในขณะที่การfindแก้ปัญหานั้นง่ายและมีประสิทธิภาพฉันตัดสินใจที่จะสร้างโซลูชันที่ซับซ้อนมากขึ้นซึ่งขึ้นอยู่กับฟังก์ชั่นที่น่าสนใจนี้ซึ่งฉันเห็นเมื่อไม่กี่วันที่ผ่านมา

  • คำอธิบายเพิ่มเติมและสองสคริปต์อื่น ๆ ขึ้นอยู่กับปัจจุบันมีไว้ที่นี่

1.สร้างไฟล์สคริปต์ที่เรียกทำงานได้ซึ่งเรียกใช้walkซึ่งอยู่ใน/usr/local/binเพื่อให้สามารถเข้าถึงได้เป็นคำสั่งเชลล์:

sudo touch /usr/local/bin/walk
sudo chmod +x /usr/local/bin/walk
sudo nano /usr/local/bin/walk
  • คัดลอกเนื้อหาของสคริปต์ด้านล่างและใช้ในnano: Shift+ Insertเพื่อวาง; Ctrl+ OและEnterสำหรับการบันทึก; Ctrl+ Xสำหรับทางออก

2.เนื้อหาของสคริปต์walkคือ:

#!/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.คำอธิบาย:

  • กลไกหลักของwalk()ฟังก์ชั่นได้อธิบายไว้สวยดีโดย Zanna ในตัวเธอคำตอบ ดังนั้นฉันจะอธิบายเฉพาะส่วนใหม่

  • ภายในwalk()ฟังก์ชั่นฉันได้เพิ่มการวนซ้ำนี้:

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

    นั่นหมายความว่าแต่ละที่เป็นไฟล์ที่จะดำเนินการฟังก์ชั่น$entryfile_specification()

  • ฟังก์ชั่นfile_specification()นี้มีสองส่วน ส่วนแรกได้รับข้อมูลที่เกี่ยวข้องกับไฟล์ - ชื่อเส้นทางขนาด ฯลฯ ส่วนที่สองส่งข้อมูลในรูปแบบที่ดี printfการจัดรูปแบบข้อมูลที่มีการใช้คำสั่ง และถ้าคุณต้องการที่จะปรับแต่งสคริปต์ที่คุณควรอ่านเกี่ยวกับคำสั่งนี้ - ตัวอย่างเช่นบทความนี้

  • ฟังก์ชั่นfile_specification()เป็นสถานที่ที่ดีที่คุณสามารถใส่คำสั่งที่ระบุว่าควรจะดำเนินการสำหรับแต่ละไฟล์ ใช้รูปแบบนี้:

    คำสั่ง "$ {รายการ}"

    หรือคุณสามารถบันทึกผลลัพธ์ของคำสั่งเป็นตัวแปรและจากนั้นprintfตัวแปรนี้ ฯลฯ :

    MY_VAR = "$ ( คำสั่ง " $ {entry} ")"
    พิมพ์ "% * s \ t ขนาดไฟล์: \ t $ {YEL}% s $ {NCL} \ n" $ ((เยื้อง + 4)) '' "$ MY_VAR"

    หรือprintfส่งออกคำสั่งโดยตรง:

    printf "% * s \ t ขนาดไฟล์: \ t $ {YEL}% s $ {NCL} \ n" $ ((เยื้อง + 4)) '' "$ ( คำสั่ง " $ {รายการ} ")"

  • ส่วนของการขอทานเรียกว่าColourise the outputเริ่มต้นตัวแปรน้อยที่ใช้ภายในprintfคำสั่งเพื่อให้สีออก เพิ่มเติมเกี่ยวกับเรื่องนี้คุณอาจพบว่าที่นี่

  • ที่ด้านล่างของใบเพิ่มเงื่อนไขเพิ่มเติมที่เกี่ยวข้องกับเส้นทางที่แน่นอนและสัมพันธ์

4.ตัวอย่างการใช้งาน:

  • วิธีเรียกใช้walkสำหรับไดเรกทอรีปัจจุบัน:

    walk      # You shouldn't use any argument, 
    walk ./   # but you can use also this format
  • วิธีเรียกใช้walkสำหรับไดเรกทอรีลูกใด ๆ :

    walk <directory name>
    walk ./<directory name>
    walk <directory name>/<sub directory>
  • วิธีเรียกใช้walkสำหรับไดเรกทอรีอื่น ๆ :

    walk /full/path/to/<directory name>
  • ในการสร้างไฟล์ข้อความตามwalkเอาต์พุต:

    walk > output.file
  • ในการสร้างไฟล์เอาต์พุตที่ไม่มีรหัสสี ( แหล่งที่มา ):

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

5. การสาธิตการใช้งาน:

ป้อนคำอธิบายรูปภาพที่นี่


นั่นเป็นงานจำนวนมาก แต่ก็ดูดี เยี่ยมมาก!
Sergiy Kolodyazhnyy

คุณใช้กระบวนการสร้าง gifs @ pa4080 เหล่านั้นอย่างไร
pbhj

@pbhj ภายใต้ Ubuntu ฉันใช้Peekมันง่ายและดี แต่บางครั้งก็ล้มเหลวและไม่มีความสามารถในการแก้ไข GIF ส่วนใหญ่ของฉันถูกสร้างขึ้นภายใต้ Windows ที่ฉันบันทึกหน้าต่างของการเชื่อมต่อ VNC ฉันมีเครื่องเดสก์ทอปที่แยกต่างหากที่ส่วนใหญ่ผมใช้สำหรับ MS Office และสร้าง GIF :) เครื่องมือที่ผมใช้มีScreenToGif มันเป็น opensource ฟรีและมีเครื่องมือแก้ไขและประมวลผลที่ทรงพลัง น่าเสียดายที่ฉันไม่พบเครื่องมือเช่น ScreenToGif สำหรับ Ubuntu
pa4080

13

ฉันงุนงงเล็กน้อยว่าทำไมไม่มีใครโพสต์เลย แต่bashมีความสามารถแบบเรียกซ้ำถ้าคุณเปิดใช้งานglobstarตัวเลือกและใช้**glob ด้วยเหตุนี้คุณสามารถเขียนbash สคริปต์บริสุทธิ์ (เกือบ) ที่ใช้ globstar แบบวนซ้ำได้ดังนี้:

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

ขอให้สังเกตว่าที่นี่เราใช้การขยายตัวพารามิเตอร์ที่จะได้รับชิ้นส่วนของชื่อไฟล์ที่เราต้องการและเราไม่ได้อาศัยคำสั่งภายนอกยกเว้นสำหรับการขนาดไฟล์ที่มีและการทำความสะอาดการส่งออกด้วยduawk

และเมื่อมันผ่านทรีไดเรกทอรีของคุณผลลัพธ์ของคุณควรเป็นดังนี้:

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

กฎระเบียบมาตรฐานของการใช้งานสคริปต์ใช้: ให้แน่ใจว่าเป็นปฏิบัติการด้วยchmod +x ./myscript.shและเรียกใช้จากไดเรกทอรีปัจจุบันผ่าน./myscript.shหรือวางไว้ในและเรียกใช้~/binsource ~/.profile


หากคุณกำลังพิมพ์ชื่อไฟล์แบบเต็ม "ส่วนขยาย" ให้พิเศษอะไรให้คุณบ้าง บางทีคุณอาจต้องการข้อมูล MIME ที่"$(file "$i")"(ในสคริปต์ด้านบนเป็นส่วนที่สองของ printf) จะกลับมา?
pbhj

1
@pbhj สำหรับฉันเป็นการส่วนตัว? ไม่มีอะไร แต่ OP ที่ถามคำถามถาม output the path, filename, extension, filesize ดังนั้นคำตอบที่ตรงกับสิ่งที่ถาม :)
Sergiy Kolodyazhnyy

12

คุณสามารถใช้findในการทำงาน

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

วิธีนี้จะช่วยคุณหากคุณต้องการแสดงรายการไฟล์ทั้งหมดที่มีขนาด

-execจะช่วยให้คุณสามารถรันคำสั่งหรือสคริปต์ที่กำหนดเองสำหรับแต่ละไฟล์ที่ \;ใช้ในการวิเคราะห์ไฟล์ทีละไฟล์คุณสามารถใช้+;หากคุณต้องการเชื่อมต่อไฟล์เหล่านั้น (หมายถึงชื่อไฟล์)


นี่เป็นสิ่งที่ดี แต่ไม่ได้ตอบทุกความต้องการของ OP ที่กล่าวถึง
αғsнιη

1
@ αғsнιηฉันแค่ให้แม่แบบแก่เขาทำงาน ฉันรู้ว่านี่ไม่ใช่คำตอบที่สมบูรณ์สำหรับคำถามนี้เพราะฉันคิดว่าคำถามนั้นกว้างในขอบเขต
Rajesh Rajendran

6

ด้วยfindเท่านั้น

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

หรือคุณสามารถใช้ด้านล่างแทน:

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
สง่างามและเรียบง่าย (ถ้าคุณรู้find -printf) +1
David Foerster

1

*ถ้าคุณรู้ว่าลึกต้นไม้เป็นวิธีที่ง่ายที่สุดจะใช้สัญลักษณ์แทน

เขียนทุกสิ่งที่คุณต้องการทำเป็นเชลล์สคริปต์หรือฟังก์ชัน

function thing() { ... }

จากนั้นเรียกใช้for i in *; do thing "$i"; done, for i in */*; do thing "$i"; done... ฯลฯ

ในฟังก์ชั่น / สคริปต์ของคุณคุณสามารถใช้การทดสอบอย่างง่าย ๆเพื่อแยกไฟล์ที่คุณต้องการใช้งานและทำสิ่งที่คุณต้องการ


"สิ่งนี้จะไม่ทำงานหากชื่อไฟล์ใด ๆ ของคุณมีช่องว่างในแฟ้ม" ... เพราะคุณลืมอ้างตัวแปรของคุณ! ใช้ "$ i" $iแทน
muru

@muru ไม่เหตุผลที่ไม่ทำงานเพราะ "for" loop splits บนช่องว่าง - " / 'ได้รับการขยายเข้าไปในรายการที่คั่นด้วยช่องว่างของไฟล์ทั้งหมดคุณสามารถหลีกเลี่ยงปัญหานี้ได้เช่นโดยยุ่งกับ IFS แต่ ณ จุดนี้คุณอาจจะใช้คำสั่งค้นพบก็ได้
นูร์เดียร์

@ pa4080 ไม่เกี่ยวข้องกับคำตอบนี้ แต่มันก็ดูมีประโยชน์มาก ๆ ขอบคุณ!
Benubird

ฉันคิดว่าคุณไม่เข้าใจวิธีการfor i in */*ทำงาน ทดสอบที่นี่:for i in */*; do printf "|%s|\n" "$i"; done
muru

นี่คือหลักฐานสำคัญของเครื่องหมายคำพูด: i.stack.imgur.com/oYSj2.png
pa4080

1

find สามารถทำได้:

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

ลองดูman findคุณสมบัติของไฟล์อื่น ๆ

หากคุณต้องการส่วนขยายจริงๆคุณสามารถเพิ่มสิ่งนี้:

find ./ -type f -printf 'Size:%s\nPath:%H\nName:%f\nExtension:' -exec sh -c 'echo "${0##*.}\n"' {} \;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.