ทุบตีรายการไฟล์วนซ้ำยกเว้นเมื่อว่างเปล่า


33

ฉันคิดว่ามันจะง่าย - แต่มันพิสูจน์ได้ซับซ้อนกว่าที่ฉันคาดไว้

ฉันต้องการวนซ้ำไฟล์ทั้งหมดของชนิดเฉพาะในไดเรกทอรีดังนั้นฉันจึงเขียนสิ่งนี้:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

ทำงานได้ตราบใดที่มีไฟล์ที่ตรงกันอย่างน้อยหนึ่งไฟล์ในไดเรกทอรี อย่างไรก็ตามหากไม่มีไฟล์ที่ตรงกันฉันจะได้รับสิ่งนี้:

current file is *.zip

ฉันลองแล้ว:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

ในขณะที่เนื้อความของลูปไม่ทำงานเมื่อไม่มีไฟล์ฉันได้รับข้อผิดพลาดจาก ls:

ls: *.zip: No such file or directory

ฉันจะเขียนลูปที่จัดการไม่มีไฟล์ที่ตรงกันได้อย่างหมดจดได้อย่างไร


7
เพิ่มshopt -s nullglobก่อนเรียกใช้ลูป for
cuonglm

@cuolnglm: ผลลัพธ์นี้ในผลลัพธ์ ls ส่งคืนชื่อของสคริปต์ที่เรียกใช้งานแทนที่จะเป็นรายการว่างในกล่อง RHEL5 นี้ (ทุบตี 3.2.25) ถ้าฉันทำFILES=ls * .zip ; for fname in "${FILES}"...แต่ทำงานตามที่คาดไว้ด้วยfor fname in *.zip ; do....
symcbean

3
ใช้ไม่ได้for file in *.zip `ls ...`คำแนะนำของ @ cuonglm นั้น*.zipจะขยายออกเป็นไม่มีอะไรเมื่อรูปแบบไม่ตรงกับไฟล์ใด ๆ lsไม่มีข้อโต้แย้งแสดงรายการไดเรกทอรีปัจจุบัน
Stéphane Chazelas

1
กล่าวถึงคำถามนี้ทำไมการแยกวิเคราะห์การส่งออกของlsทั่วไปที่ควรหลีกเลี่ยง: ทำไมไม่แยกls? ; ดูลิงค์ใกล้ด้านบนของหน้านั้นไปยังบทความParsingLsของBashGuide
PM 2Ring

คำตอบ:


34

ในbashคุณสามารถตั้งค่าnullglobตัวเลือกเพื่อให้รูปแบบที่ตรงกับสิ่งใด "หายไป" แทนที่จะถือเป็นสตริงตัวอักษร:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

ใน POSIX เชลล์สคริปต์คุณเพียงแค่ตรวจสอบว่าfnameมีอยู่แล้ว (และในเวลาเดียวกันด้วย[ -f ]ให้ตรวจสอบว่าเป็นไฟล์ปกติ (หรือ symlink ไปยังไฟล์ปกติ) และไม่ใช่ประเภทอื่น ๆ เช่น directory / fifo / device ... ):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

แทนที่[ -f "$fname" ]ด้วย[ -e "$fname" ] || [ -L "$fname ]หากคุณต้องการวนซ้ำไฟล์ทั้งหมด (ไม่ซ่อน) ที่มีชื่อลงท้ายด้วย.zipไม่ว่าจะเป็นไฟล์ประเภทใด

แทนที่*.zipด้วย.*.zip .zip *.zipหากคุณต้องการพิจารณาไฟล์ที่ซ่อนซึ่งมีชื่อลงท้าย.zipด้วย


1
shopt -s nullglobไม่ได้ผลสำหรับฉันบน Ubuntu 17.04 แต่[ -f "$fname" ] || continueทำงานได้ดี
koppor

@koppor bashมันเสียงเหมือนคุณไม่ได้ใช้จริง
chepner

2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

ในความคิดเห็นที่นี่คุณพูดถึงการเรียกใช้ฟังก์ชั่น ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *

2

ใช้ค้นหา

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

คุณต้องส่งออกฟังก์ชั่นเชลล์ของคุณexport -fเพื่อให้สามารถใช้งานได้ ตอนนี้findจะทำการประมวลbashผลฟังก์ชันเชลล์ของคุณและยังคงอยู่ที่ระดับ dir ปัจจุบันเท่านั้น


ซึ่งเกิดขึ้นซ้ำผ่านไดเรกทอรีย่อยและฉันต้องการเรียกใช้ฟังก์ชันทุบตี (ไม่ใช่สคริปต์) สำหรับการแข่งขัน
symcbean

@symcbean ฉันได้แก้ไขเพื่อ จำกัด dir เดียวและจัดการฟังก์ชั่นทุบตี
Dani_l

-3

แทน:

FILES=`ls *.zip`

ลอง:

FILES=`ls * | grep *.zip`

วิธีนี้ถ้า ls ล้มเหลว (ซึ่งทำในกรณีของคุณ) มันจะ grep ผลลัพธ์ที่ล้มเหลวและกลับมาเป็นตัวแปรว่าง

current file is      <---Blank Here

คุณสามารถเพิ่มตรรกะในสิ่งนี้เพื่อทำให้มันกลับมา "ไม่พบไฟล์"

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

วิธีนี้หากคำสั่งก่อนหน้าประสบความสำเร็จ (ออกด้วยค่า 0) มันจะพิมพ์ไฟล์ปัจจุบันมิฉะนั้นจะพิมพ์ "ไม่พบไฟล์"


1
ฉันคิดว่าเป็นความคิดที่ดีที่จะเพิ่มอีกหนึ่งกระบวนการ ( grep) แทนที่จะพยายามแก้ไขปัญหาโดยใช้เครื่องมือที่ดีกว่า ( find) หรือเปลี่ยนการตั้งค่าที่เกี่ยวข้องสำหรับโซลูชันปัจจุบัน (พร้อมshopt -s nullglob)
Thomas Baruchel

1
ตามความคิดเห็นของ OP ในโพสต์ดั้งเดิมของพวกเขาshopt -s nullglobไม่ทำงาน ฉันพยายามfindตรวจสอบคำตอบแล้วก็ล้มเหลว ฉันคิดว่าเพราะสิ่งที่ดานิพูดออกมา
Kip K
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.