Bash - ตรวจสอบไดเรกทอรีของไฟล์จากรายการชื่อไฟล์บางส่วน


8

ฉันมีเซิร์ฟเวอร์ที่รับไฟล์ต่อไคลเอนต์ในแต่ละวันลงในไดเรกทอรี ชื่อไฟล์ถูกสร้างขึ้นดังนี้:

uuid_datestring_other-data

ตัวอย่างเช่น:

d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
  • uuid เป็นรูปแบบมาตรฐาน uuid
  • datestringdate +%Y%m%dคือผลลัพธ์จาก
  • other-data มีความยาวผันแปรได้ แต่จะไม่มีขีดล่าง

ฉันมีไฟล์รูปแบบ:

#
d6f60016-0011-49c4-8fca-e2b3496ad5a7    client1
d5873483-5b98-4895-ab09-9891d80a13da    client2
be0ed6a6-e73a-4f33-b755-47226ff22401    another_client
...

ฉันต้องตรวจสอบว่า uuid ทุกรายการในไฟล์มีไฟล์ที่เกี่ยวข้องในไดเรกทอรีโดยใช้ bash

ฉันได้มาไกลขนาดนี้ แต่รู้สึกว่าฉันมาจากทิศทางที่ผิดโดยใช้คำสั่ง if และฉันต้องวนซ้ำไฟล์ต่างๆในไดเรกทอรีต้นทาง

ตัวแปร source_directory และ uuid_list ได้รับการกำหนดไว้ก่อนหน้าในสคริปต์:

# Check the entries in the file list

while read -r uuid name; do
# Ignore comment lines
   [[ $uuid = \#* ]] && continue
   if [[ -f "${source_directory}/${uuid}*" ]]
   then
      echo "File for ${name} has arrived"
   else
      echo "PANIC! - No File for ${name}"
   fi
done < "${uuid_list}"

ฉันจะตรวจสอบว่าไฟล์ในรายการของฉันมีอยู่ในไดเรกทอรีได้อย่างไร? ฉันต้องการใช้ฟังก์ชันทุบตีเท่าที่จะทำได้ แต่ไม่ขัดกับการใช้คำสั่งหากจำเป็น


งูใหญ่? และไดเรกทอรีเซิร์ฟเวอร์ "แบน" คืออะไร?
Jacob Vlijm

ใช่มันแบนไม่มีไดเรกทอรีย่อย ฉันควรจะติดกับทุบตีถ้าเป็นไปได้
Arronical

1
ตกลงฉันจะไม่โพสต์
Jacob Vlijm


ฉันไม่เห็นว่ามีอะไรผิดปกติกับสิ่งที่คุณมี คุณจะต้องวนซ้ำทั้ง UUID หรือไฟล์ทำไมวงหนึ่งถึงดีกว่าอีกวงล่ะ
terdon

คำตอบ:


5

เดินข้ามไฟล์สร้างอาเรย์แบบเชื่อมโยงเหนือ uuids ที่มีอยู่ในชื่อ (ฉันใช้การขยายพารามิเตอร์เพื่อแยก uuid) อ่านรายการตรวจสอบอาเรย์แบบเชื่อมโยงสำหรับแต่ละ uuid และรายงานว่าไฟล์ถูกบันทึกหรือไม่

#!/bin/bash
uuid_list=...

declare -A file_for
for file in *_*_* ; do
    uuid=${file%%_*}
    file_for[$uuid]=1
done

while read -r uuid name ; do
    [[ $uuid = \#* ]] && continue
    if [[ ${file_for[$uuid]} ]] ; then
        echo "File for $name has arrived."
    else
        echo "File for $name missing!"
    fi
done < "$uuid_list"

1
ดี (+1) แต่ทำไมสิ่งนี้ถึงดีกว่า OP ที่ทำอยู่ คุณดูเหมือนจะทำสิ่งพื้นฐานเดียวกัน แต่ในสองขั้นตอนแทนที่จะเป็นหนึ่ง
terdon

1
@terdon: ความแตกต่างหลักคือมันใช้งานได้ :-) การขยายไวด์การ์ดทำได้เพียงครั้งเดียวไม่ใช่ทุกครั้งที่คุณอ่านบรรทัดจากรายการซึ่งอาจเร็วกว่าด้วย
choroba

ใช่นั่นคือความแตกต่างที่สำคัญ ยุติธรรมพอ :)
terdon

นี่คือสิ่งที่มหัศจรรย์มากขอบคุณ +1 ของฉันมาให้ มีวิธีการรวมเส้นทางไปยังไดเรกทอรีที่เก็บไฟล์หรือไม่ ฉันรู้ว่าฉันสามารถcdเข้าไปในไดเรกทอรีภายในสคริปต์ได้ แต่ก็สงสัยว่าเพื่อให้ได้ความรู้
Arronical

@Arronical: มันเป็นไปได้ file=${file##*/}แต่คุณจะต้องลบเส้นทางจากสตริงที่เป็นไปได้ด้วย
choroba

5

ต่อไปนี้เป็นวิธี "bashy" และกระชับยิ่งขึ้น:

#!/bin/bash

## Read the UUIDs into the array 'uuids'. Using awk
## lets us both skip comments and only keep the UUID
mapfile -t uuids < <(awk '!/^\s*#/{print $1}' uuids.txt)

## Iterate over each UUID
for uuid in ${uuids[@]}; do
        ## Set the special array $_ (the positional parameters: $1, $2 etc)
        ## to the glob matching the UUID. This will be all file/directory
        ## names that start with this UUID.
        set -- "${source_directory}"/"${uuid}"*
        ## If no files matched the glob, no file named $1 will exist
        [[ -e "$1" ]] && echo "YES : $1" || echo  "PANIC $uuid" 
done

โปรดทราบว่าในขณะที่ข้างต้นค่อนข้างสวยและจะทำงานได้ดีสำหรับไฟล์บางไฟล์ความเร็วของมันขึ้นอยู่กับจำนวนของ UUID และจะช้ามากหากคุณต้องการประมวลผลจำนวนมาก หากเป็นเช่นนั้นให้ใช้วิธีแก้ปัญหาของ @ choroba หรือเพื่อบางสิ่งที่รวดเร็วอย่างแท้จริงให้หลีกเลี่ยงเชลล์และการโทรperl:

#!/bin/bash

source_directory="."
perl -lne 'BEGIN{
            opendir(D,"'"$source_directory"'"); 
            foreach(readdir(D)){ /((.+?)_.*)/; $f{$2}=$1; }
           } 
           s/\s.*//; $f{$_} ? print "YES: $f{$_}" : print "PANIC: $_"' uuids.txt

เพียงเพื่อแสดงให้เห็นถึงความแตกต่างของเวลาฉันทดสอบวิธีทุบตีของฉัน choroba และ perl ของฉันในไฟล์ที่มี 20,000 UUIDs ซึ่ง 18001 นั้นมีชื่อไฟล์ตรงกัน /dev/nullโปรดทราบว่าแต่ละการทดสอบดำเนินการโดยเปลี่ยนเส้นทางออกสคริปต์ที่จะ

  1. ทุบตีของฉัน (~ 3.5 นาที)

    real   3m39.775s
    user   1m26.083s
    sys    2m13.400s
  2. Choroba's (ทุบตี, 0.7 วินาที)

    real   0m0.732s
    user   0m0.697s
    sys    0m0.037s
  3. Perl ของฉัน (~ 0.1 วินาที):

    real   0m0.100s
    user   0m0.093s
    sys    0m0.013s

+1 สำหรับวิธีรัดกุมน่าจะต้องถูกเรียกใช้จากภายในไดเรกทอรีที่มีไฟล์ ฉันรู้ว่าฉันสามารถcdใส่ไดเรคทอรี่ในสคริปต์ได้ แต่มีวิธีที่พา ธ ของไฟล์จะรวมอยู่ในการค้นหาหรือไม่
Arronical

@Arronical แน่ใจดูคำตอบที่ปรับปรุง คุณสามารถใช้${source_directory}เช่นเดียวกับที่คุณทำในสคริปต์ของคุณ
terdon

หรือใช้"$2"และส่งผ่านไปยังสคริปต์เป็นอาร์กิวเมนต์ที่สอง
alexis

ตรวจสอบว่ามันทำงานเร็วพอสำหรับจุดประสงค์ของคุณ - เร็วกว่าที่จะทำด้วยการสแกนไดเรกทอรีเดียวแทนที่จะค้นหาไฟล์จำนวนมากเช่นนี้
alexis

1
@alexis ใช่คุณพูดถูก ฉันทำการทดสอบและนี่จะช้ามากหากจำนวนของ UUIDs / ไฟล์เพิ่มขึ้น ฉันได้เพิ่มวิธี perl (ซึ่งสามารถเรียกใช้เป็นหนึ่งซับจากภายในสคริปต์ทุบตีดังนั้นเทคนิคยังคงทุบตีถ้าคุณเปิดการตั้งชื่อสร้างสรรค์) ซึ่งเร็วกว่ามาก
terdon

3

นี่คือ Bash ที่บริสุทธิ์ (เช่นไม่มีคำสั่งจากภายนอก) และเป็นวิธีที่ตรงที่สุดที่ฉันสามารถนึกถึง

แต่ประสิทธิภาพที่ฉลาดนั้นไม่ได้ดีไปกว่าสิ่งที่คุณมีอยู่ในปัจจุบัน

มันจะอ่านแต่ละบรรทัดจากpath/to/file; สำหรับแต่ละบรรทัดก็จะเก็บข้อมูลครั้งแรกใน$uuidและพิมพ์ข้อความว่าไฟล์ตรงกับรูปแบบpath/to/directory/$uuid*จะไม่ได้พบ:

#! /bin/bash
[ -z "$2" ] && printf 'Not enough arguments.\n' && exit

while read uuid; do
    [ ! -f "$2/$uuid"* ] && printf '%s missing in %s\n' "$uuid" "$2"
done <"$1"

path/to/script path/to/file path/to/directoryเรียกมันว่าด้วย

เอาต์พุตตัวอย่างโดยใช้ไฟล์อินพุตตัวอย่างในคำถามบนลำดับชั้นไดเร็กทอรีการทดสอบที่มีไฟล์ตัวอย่างในคำถาม:

% tree
.
├── path
│   └── to
│       ├── directory
│       │   └── d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
│       └── file
└── script.sh

3 directories, 3 files
% ./script.sh path/to/file path/to/directory
d5873483-5b98-4895-ab09-9891d80a13da* missing in path/to/directory
be0ed6a6-e73a-4f33-b755-47226ff22401* missing in path/to/directory

3
unset IFS
set -f
set +f -- $(<uuid_file)
while  [ "${1+:}" ]
do     : < "$source_directory/$1"*  &&
       printf 'File for %s has arrived.\n' "$2"
       shift 2
done

แนวคิดนี้ไม่ต้องกังวลเกี่ยวกับการรายงานข้อผิดพลาดเชลล์จะรายงานให้คุณทราบ หากคุณพยายามที่จะ<เปิดไฟล์ที่ไม่มีเปลือกของคุณจะบ่น ในความเป็นจริงมันจะเพิ่มสคริปต์ของคุณ$0และหมายเลขบรรทัดที่ข้อผิดพลาดเกิดขึ้นกับเอาต์พุตข้อผิดพลาดเมื่อมันเกิดขึ้น ... นี่เป็นข้อมูลที่ดีที่มีให้ตามค่าเริ่มต้นแล้ว - ดังนั้นอย่ากังวล

คุณไม่จำเป็นต้องใช้ไฟล์แบบทีละบรรทัดเพราะมันอาจช้ามาก นี่เป็นการขยายสิ่งทั้งหมดในช็อตเดียวไปสู่อาเรย์ของการขัดแย้งที่มีการเว้นวรรคและมันจัดการสองครั้ง ถ้าข้อมูลของคุณมีความสอดคล้องกับตัวอย่างของคุณแล้ว$1จะเป็น UUID ของคุณและจะเป็นของคุณ$2 $nameหากbashสามารถเปิดการแข่งขันให้กับ uuid ของคุณ - และมีการแข่งขันดังกล่าวเพียงรายการเดียวเท่านั้น- printfจะเกิดขึ้น ไม่เช่นนั้นเชลล์จะไม่เขียนและวินิจฉัยเพื่อ stderr เกี่ยวกับสาเหตุ


1
@kos - มีไฟล์อยู่หรือไม่ ถ้าไม่เช่นนั้นจะทำงานตามที่ตั้งใจไว้ unset IFSตรวจสอบให้แน่ใจว่า$(cat <uuid_file)มีการแบ่งบนพื้นที่สีขาว เปลือกหอย$IFSแตกต่างกันเมื่อมันประกอบไปด้วยพื้นที่สีขาวเท่านั้นหรือไม่มีการตั้งค่า การขยายแบบแยกดังกล่าวไม่เคยมีเขตข้อมูลว่างใด ๆ เนื่องจากลำดับพื้นที่สีขาวทั้งหมดอยู่ในฐานะเป็นตัวคั่นเขตข้อมูลเดียว ตราบใดที่มีเพียงสองช่องว่างที่ไม่ได้เว้นวรรคในแต่ละบรรทัดที่ควรทำงานฉันคิดว่า ในbashอยู่แล้ว set -fตรวจสอบให้แน่ใจว่าการขยายตัวที่ไม่ได้ยกมานั้นไม่ได้ถูกตีความสำหรับ globs และเซต + f ทำให้แน่ใจได้ว่า globs ในภายหลังนั้น
mikeserv

@kos - ฉันเพิ่งแก้ไข ฉันไม่ควรใช้<>เพราะมันสร้างไฟล์ที่ไม่มีอยู่จริง <จะรายงานตามที่ฉันต้องการ ปัญหาที่เป็นไปได้กับที่ - และเหตุผลที่ฉันใช้ไม่ถูกต้อง<>ในสถานที่แรก - คือถ้ามันเป็นไฟล์ท่อโดยไม่ต้องอ่านหรือเช่น dev dev บรรทัดบัฟเฟอร์มันจะแขวน [ -f "$dir/$1"* ]ที่อาจจะหลีกเลี่ยงโดยการจัดการออกข้อผิดพลาดมากขึ้นอย่างชัดเจนและการทำ เรากำลังพูดถึง uuids ที่นี่และดังนั้นจึงไม่ควรขยายไปมากกว่าไฟล์เดียว มันค่อนข้างดีแม้ว่าจะรายงานชื่อไฟล์ที่ล้มเหลวไปยัง stderr เช่นนั้น
mikeserv

@kos - จริง ๆ แล้วฉันคิดว่าฉันสามารถใช้ ulimit เพื่อป้องกันไม่ให้สร้างไฟล์ใด ๆ เลยและดังนั้น<>จะยังคงใช้งานได้ด้วยวิธี<>นี้... จะดีกว่าถ้า glob อาจขยายไปยังไดเรกทอรีเพราะบน linux การอ่าน / เขียนจะ ล้มเหลวและพูด - นั่นคือไดเรกทอรี
mikeserv

@kos - โอ้! ฉันขอโทษ - ฉันแค่โง่ - คุณมีคู่ที่ตรงกันและมันทำในสิ่งที่ถูกต้อง ฉันหมายความว่ามันผิดพลาดในแบบนั้นถ้ามีการจับคู่สองรายการพวกนี้ควรจะเป็น uuids - ไม่ควรมีชื่อที่คล้ายกัน 2 ชื่อที่ตรงกับ glob เดียวกัน thats เจตนาอย่างเต็มที่ - และมันเป็น ที่คลุมเครือในทางที่มันควรจะเป็น คุณเห็นสิ่งที่ฉันหมายถึงอะไร การตั้งชื่อไฟล์สำหรับ glob ไม่ใช่ปัญหา - ตัวอักษรพิเศษไม่เกี่ยวข้องกันที่นี่ - ปัญหาคือbashจะยอมรับการเปลี่ยนเส้นทางแบบหมุนได้หากมันตรงกับไฟล์เดียวเท่านั้น ดูที่man bashREDIRECTION
mikeserv

1

วิธีที่ฉันจะเข้าใกล้มันคือการรับ uuids จากไฟล์ก่อนจากนั้นใช้ find

awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;done

เพื่อความสะดวก

awk '{print $1}' listfile.txt  | \
    while read fileName;do \
    find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null;
    done

ตัวอย่างที่มีรายชื่อของไฟล์ที่/etc/กำลังมองหา passwd กลุ่ม fstab และชื่อไฟล์ ThisDOESNTEXIST

$ awk '{print $1}' listfile.txt  | while read fileName;do find /etc -name "$fileName*" -printf "%p FOUND\n" 2> /dev/null; done
/etc/pam.d/passwd FOUND
/etc/cron.daily/passwd FOUND
/etc/passwd FOUND
/etc/group FOUND
/etc/iproute2/group FOUND
/etc/fstab FOUND

เมื่อคุณพูดถึงว่าไดเรกทอรีนั้นเรียบคุณสามารถใช้-printf "%f\n"ตัวเลือกเพื่อพิมพ์ชื่อไฟล์เอง

สิ่งนี้ไม่ได้ทำคือการแสดงรายการไฟล์ที่หายไป findข้อเสียเล็ก ๆ น้อย ๆ คือมันไม่ได้บอกคุณว่ามันหาไฟล์ไม่ได้ก็ต่อเมื่อตรงกับบางอย่าง อย่างไรก็ตามสิ่งหนึ่งที่สามารถทำได้คือการตรวจสอบเอาท์พุท - ถ้าเอาท์พุทว่างเปล่าเรามีไฟล์หายไป

awk '{print $1}' listfile.txt  | while read fileName;do RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; [ -z "$RESULT"  ] && echo "$fileName not found" || echo "$fileName found"  ;done

อ่านเพิ่มเติมได้:

awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

และนี่คือวิธีที่มันทำงานเป็นสคริปต์ขนาดเล็ก:

skolodya@ubuntu:$ ./listfiles.sh                                               
passwd found
group found
fstab found
THISDONTEXIST not found

skolodya@ubuntu:$ cat listfiles.sh                                             
#!/bin/bash
awk '{print $1}' listfile.txt  | \
   while read fileName;do \
   RESULT="$(find /etc -name "$fileName*" -printf "%p\n" 2> /dev/null )"; \
   [ -z "$RESULT"  ] && echo "$fileName not found" || \
   echo "$fileName found"  
   done

หนึ่งสามารถใช้statเป็นทางเลือกเนื่องจากมันเป็นไดเร็กตอรี่แฟลต, แต่ตะโกนรหัสจะไม่ทำงานซ้ำสำหรับไดเรกทอรีย่อยถ้าคุณเคยตัดสินใจที่จะเพิ่ม:

$ awk '{print $1}' listfile.txt  | while read fileName;do  stat /etc/"$fileName"* 1> /dev/null ;done        
stat: cannot stat ‘/etc/THISDONTEXIST*’: No such file or directory

หากเรานำstatแนวคิดนี้ไปใช้เราสามารถใช้รหัสทางออกของสถิติเป็นตัวบ่งชี้ว่ามีไฟล์อยู่หรือไม่ Effectivelly เราต้องการทำสิ่งนี้:

$ awk '{print $1}' listfile.txt  | while read fileName;do  if stat /etc/"$fileName"* &> /dev/null;then echo "$fileName found"; else echo "$fileName NOT found"; fi ;done

วิ่งตัวอย่าง:

skolodya@ubuntu:$ awk '{print $1}' listfile.txt  | \                                                         
> while read FILE; do                                                                                        
> if stat /etc/"$FILE" &> /dev/null  ;then                                                                   
> echo "$FILE found"                                                                                         
> else echo "$FILE NOT found"                                                                                
> fi                                                                                                         
> done
passwd found
group found
fstab found
THISDONTEXIST NOT found
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.