ทดสอบว่ามีไฟล์ที่ตรงกับรูปแบบเพื่อเรียกใช้สคริปต์หรือไม่


29

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

รหัสของฉันในปัจจุบัน:

if [ -f /*.txt ]; then ./script fi

โปรดให้แนวคิดบางอย่าง; ฉันต้องการเรียกใช้สคริปต์หากมีอยู่.txtในไดเรกทอรีเท่านั้น


3
คุณแน่ใจหรือไม่ว่า "ไดเรกทอรี" ควรเป็น/? fiนอกจากนี้คุณกำลังขาดหายไปอัฒภาคก่อน
ตัดสิทธิ์

วิธีการแก้ปัญหาที่มีประสิทธิภาพสะอาดที่ฉันได้พบคือการใช้งานfindตามที่อธิบายไว้ที่นี่ใน StackOverflow
Joshua Goldberg

คำตอบ:


39
[ -f /*.txt ]

จะกลับมาจริงเฉพาะถ้ามีหนึ่ง (และมีเพียงหนึ่ง) ไฟล์ที่ไม่ใช่ที่ซ่อนอยู่ใน/ที่มีชื่อในปลาย.txtและหากไฟล์ที่เป็นไฟล์ปกติหรือ symlink ไปเป็นไฟล์ปกติ

นั่นเป็นเพราะเชลล์จะถูกขยายโดยเชลล์ก่อนที่จะถูกส่งไปยังคำสั่ง (ที่นี่[)

ดังนั้นถ้ามี/a.txtและ/b.txt, [จะถูกส่งผ่าน 5 ข้อโต้แย้ง: [, -f, /a.txt, และ/b.txt จะบ่นว่าได้รับข้อโต้แย้งมากเกินไป][-f

หากคุณต้องการตรวจสอบว่า*.txtรูปแบบขยายเป็นไฟล์ที่ไม่ซ่อนอย่างน้อยหนึ่งไฟล์ (ปกติหรือไม่):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobเป็นbashเฉพาะ แต่เปลือกหอยชอบksh93, zsh, yash, tcshมีงบเทียบเท่า

โปรดทราบว่ามันค้นหาไฟล์เหล่านั้นโดยการอ่านเนื้อหาของไดเรกทอรีมันไม่ลองและเข้าถึงไฟล์เหล่านั้นเลยซึ่งทำให้มีประสิทธิภาพมากกว่าโซลูชันที่เรียกคำสั่งเช่นlsหรือstatในรายการของไฟล์ที่คำนวณโดยเชลล์

มาตรฐานที่shเทียบเท่าจะเป็น:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

ปัญหาคือว่าด้วย Bourne หรือ POSIX shells ถ้ารูปแบบไม่ตรงกันมันจะขยายออกไปเอง ดังนั้นถ้า*.txtขยายไปยัง*.txtคุณไม่ทราบว่ามันเป็นเพราะไม่มีแฟ้มในไดเรกทอรีหรือเพราะมีไฟล์เดียวที่เรียกว่า.txt *.txtการใช้[*].txt *.txtช่วยให้สามารถแยกแยะระหว่างสอง


[ -f /*.txt ]compgenค่อนข้างรวดเร็วในการเปรียบเทียบกับ
Daniel Böhmer

@ DanielBöhmer [ -f /*.txt ]จะผิด แต่ในการทดสอบของฉันในไดเรกทอรีที่มี3425ไฟล์94ซึ่งเป็นไฟล์ txt ที่ไม่ถูกซ่อนcompgen -G "*.txt" > /dev/null 2>&1จะปรากฏเร็วเท่ากับset -- *.txt; [ "$#" -gt 0 ](20.5 วินาทีสำหรับทั้งสองเมื่อทำซ้ำ 10,000 ครั้งในกรณีของฉัน)
Stéphane Chazelas

11

คุณสามารถใช้find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

คำอธิบาย:

  • find . : ค้นหาไดเรกทอรีปัจจุบัน
  • -maxdepth 1: ห้ามค้นหาไดเรกทอรีย่อย
  • -type f : ค้นหาไฟล์ปกติเท่านั้น
  • name "*.txt" : ค้นหาไฟล์ที่ลงท้ายด้วย .txt
  • 2>/dev/null : เปลี่ยนเส้นทางข้อความผิดพลาดไปที่ /dev/null
  • | grep -q . : grep สำหรับตัวละครใด ๆ จะคืนค่าเท็จหากไม่พบอักขระ
  • && ./script: ดำเนินการ./scriptเฉพาะเมื่อคำสั่งก่อนหน้านี้ประสบความสำเร็จ ( &&)

2
findคืนค่าเท็จถ้ามันมีปัญหาในการค้นหาไฟล์ไม่ใช่ถ้ามันไม่พบไฟล์ใด ๆ คุณต้องการไพพ์เอาต์พุตgrep -q .เพื่อตรวจสอบว่าพบบางอย่าง
Stéphane Chazelas

@ StephanChazelas คุณค่อนข้างถูกต้อง แปลก แต่ฉันได้ทดสอบแล้วและดูเหมือนว่าจะทำงาน ต้องทำสิ่งที่แปลกเพราะมันไม่ได้อีกต่อไป เมื่อใดจะพบ "มีปัญหาในการค้นหาไฟล์"
terdon

@terdon เช่นเมื่อบางไดเร็กทอรีไม่สามารถเข้าถึงได้หรือข้อผิดพลาด I / O หรือข้อผิดพลาดใด ๆ ที่ส่งคืนโดยการเรียกระบบใด ๆ chmod a-x .ในกรณีที่พยายามหลังจาก
Stéphane Chazelas

8

compgenวิธีการแก้ปัญหาที่เป็นไปได้ยังเป็นทุบตีในตัว คำสั่งนั้นส่งคืนการจับคู่ที่เป็นไปได้ทั้งหมดสำหรับรูปแบบการวนรอบและมีรหัสการออกที่ระบุว่ามีไฟล์ใดตรงกันหรือไม่

compgen -G "/*.text" > /dev/null && ./script

ฉันพบคำถามนี้ในขณะที่มองหาวิธีแก้ปัญหาที่รวดเร็วยิ่งขึ้น


1
หาดี! LC_ALL=C compgen -G "*.txt" > /dev/nullถ้าคุณอยู่ในสถานที่เกิดเหตุหลายไบต์ที่คุณสามารถปรับปรุงเล็กน้อย
Stéphane Chazelas

7

นี่คือหนึ่งซับที่จะทำ:

$ ls
file1.pl  file2.pl

มีไฟล์อยู่

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

ไม่มีไฟล์

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

วิธีนี้ทำให้การใช้งานของ||และ&&ผู้ประกอบการในทุบตี นี่คือตัวดำเนินการ "หรือ" และ "และ"

ดังนั้นถ้าคำสั่ง stat คืน$?ค่าเท่ากับ 0 ดังนั้นechoจะเรียกชื่อแรกถ้ามันคืนค่า 1 ดังนั้นechoจะเรียกชื่อที่สอง

ส่งคืนผลลัพธ์จากสถิติ

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

คำถามนี้ครอบคลุมอย่างครอบคลุมใน stackoverflow:


1
เหตุใดจึงต้องใช้ที่ไม่ได้มาตรฐานstatเมื่อls -dสามารถทำได้เหมือนกัน
Stéphane Chazelas

ฉันคิดว่าls -dรายการไดเรกทอรีหรือไม่? ดูเหมือนจะไม่ทำงานเมื่อฉันพยายามรายชื่อไดเรกทอรีกับไฟล์ในนั้นls -d *.plตัวอย่างเช่น
slm

คุณสามารถแทนที่คำสั่งทางด้านซ้ายของ&&โดยls *.txtและมันจะทำงานเช่นกัน ตรวจสอบให้แน่ใจว่าคุณส่ง stdout และ stderr ไป/dev/nullตามที่แนะนำโดย @slm
unxnut

1
ถ้าคุณใช้ls *.txtและไม่มีไฟล์ปัจจุบันในสารบบนี้จะกลับ$? = 2ซึ่งจะยังคงทำงานร่วมกับถ้าแล้ว แต่นี่เป็นหนึ่งในเหตุผลของฉันสำหรับการเลือกมากกว่าstat lsฉันต้องการ 0 เพื่อความสำเร็จและ 1 สำหรับความล้มเหลว
slm

ls -dคือรายการไดเรกทอรีแทนเนื้อหา ดังนั้นls -dเพียงแค่ไม่lstatที่ไฟล์เช่นเดียวกับ GNU statไม่ คำสั่งสถานะออกที่ไม่เป็นศูนย์ใด ๆ ที่ส่งคืนเมื่อเกิดความล้มเหลวคือระบบที่เฉพาะเจาะจงจึงไม่มีเหตุผลที่จะตั้งสมมติฐานให้กับคำสั่งเหล่านั้น
Stéphane Chazelas

4

เมื่อ Chazelas ชี้ให้เห็นสคริปต์ของคุณจะล้มเหลวหากการขยายตัวของ wildcard ตรงกับมากกว่าหนึ่งไฟล์

อย่างไรก็ตามมีกลอุบายที่ฉันใช้ ( แม้ว่าฉันจะไม่ชอบมันมากนัก ) ที่จะหลีกเลี่ยง:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

มันทำงานอย่างไร?

การขยายตัวของสัญลักษณ์แทนจะตรงกับชื่อไฟล์หลายชุดเราจะได้ชื่อแรกถ้ามีบางส่วนไม่เช่นนั้นจะเป็นโมฆะหากไม่มีการจับคู่


IMO นี่เป็นคำตอบที่แย่ที่สุดที่นี่ พวกเขาดูเหมือนจะน่ากลัว แต่ดูเหมือนว่าคุณสมบัติพื้นฐานจะหายไปจากภาษา
plugwash

@plugwash มันเป็นความตั้งใจ ... * สคริปต์ shell nix มีการควบคุมการไหลพื้นฐานและอัตราต่อรองและอื่น ๆ ไม่กี่ แต่ในตอนท้ายของวันมันเป็นงานที่จะติดกันคำสั่งอื่น ๆ หากทุบตีครับ ... มันเป็นเพราะคำสั่งที่คุณใช้จากมันดูด
cb88

2
นั่นเป็นตรรกะที่ผิด (และคุณไม่มีเครื่องหมายคำพูด) ตรวจสอบว่าไฟล์แรกที่ตรงกันเป็นไฟล์ปกติหรือไม่ มันอาจจะดีไฟล์ที่ไม่ปกติ แต่อาจจะมีอีกหลาย.txtไฟล์ที่เป็นประเภทปกติ mkdir a.txt; mkfifo b.txt; echo regular > c.txtลองยกตัวอย่างเช่นหลังจากที่
Stéphane Chazelas

1

ง่ายเหมือน:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l นับบรรทัดใน wildcard ที่ขยาย


1
Downvote: นี่เป็นการรวบรวมจำนวน antipatterns เริ่มต้นที่น่าทึ่งในโค้ดจำนวนเล็กน้อย คุณไม่ควรแยกวิเคราะห์lsผลลัพธ์และแทบไม่เคยตรวจสอบ$?โดยตรงเพราะifทำเช่นนั้น นอกจากนี้ยังใช้wcเพื่อดูว่าสิ่งที่เกิดขึ้นนั้นผิดไปในทำนองเดียวกัน
tripleee

0

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

นี่เป็นโครงสร้างสำรองที่ฉันทดสอบเมื่อวานนี้:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

ค่าออกจาก grep ดูเหมือนจะกำหนดเส้นทางผ่านโครงสร้างการควบคุม นอกจากนี้ยังทดสอบด้วยนิพจน์ทั่วไปแทนรูปแบบเชลล์ ระบบของฉันบางระบบมีคำสั่ง "pcregrep" ซึ่งให้การจับคู่ regex ที่ซับซ้อนยิ่งขึ้น

(ฉันแก้ไขคำตอบนี้เพื่อลบ "ls" ในการทดแทนคำสั่งหลังจากอ่านคำวิจารณ์ด้านบนเพื่อวิเคราะห์คำ)


-2

หากคุณต้องการใช้ประโยค if ให้ประเมินจำนวน:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.