คำตอบ tldr ของฉันคือ:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
มันเป็นไปตาม POSIX และไม่ว่ามันจะสำคัญมาก แต่โดยทั่วไปจะเร็วกว่าโซลูชันที่แสดงรายการไดเรกทอรีและไพพ์เอาต์พุตเป็น grep
การใช้งาน:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
ฉันชอบคำตอบhttps://unix.stackexchange.com/a/202276/160204ซึ่งฉันเขียนใหม่เป็น:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
มันแสดงรายการไดเรกทอรีและไพพ์ผลลัพธ์เป็น grep ฉันขอเสนอฟังก์ชั่นที่เรียบง่ายซึ่งมีพื้นฐานมาจากการขยายตัวและการเปรียบเทียบแบบกลม
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
ฟังก์ชั่นนี้ไม่ได้มาตรฐาน POSIX และเรียกร้อง subshell $()
ด้วย ฉันอธิบายฟังก์ชั่นง่าย ๆ นี้ก่อนเพื่อให้เราสามารถเข้าใจวิธีแก้ปัญหาสุดท้ายได้ดีขึ้น (ดูคำตอบ tldr ด้านบน) ในภายหลัง
คำอธิบาย:
ด้านซ้ายมือ (LHS) จะว่างเปล่าเมื่อไม่มีการขยายเกิดขึ้นซึ่งเป็นกรณีเมื่อไดเรกทอรีว่างเปล่า จำเป็นต้องมีตัวเลือก nullglob เพราะไม่เช่นนั้นเมื่อไม่มีการจับคู่แล้วตัว glob จะเป็นผลลัพธ์ของการขยายตัว (การมี RHS ตรงกับ globs ของ LHS เมื่อไดเรกทอรีว่างเปล่าไม่ทำงานเนื่องจากผลบวกปลอมที่เกิดขึ้นเมื่อ LHS glob ตรงกับไฟล์เดียวชื่อเป็น glob ตัวเอง: *
ใน glob ตรงกับสตริงย่อย*
ในชื่อไฟล์ ) รั้งแสดงออก{,.[^.],..?}
ครอบคลุมไฟล์ที่ซ่อนอยู่ แต่ไม่ได้ หรือ..
.
เนื่องจากshopt -s nullglob
ถูกดำเนินการภายใน$()
(subshell) จึงไม่เปลี่ยนตัวnullglob
เลือกของเชลล์ปัจจุบันซึ่งโดยปกติจะเป็นสิ่งที่ดี ในทางตรงกันข้ามมันเป็นความคิดที่ดีที่จะตั้งค่าตัวเลือกนี้ในสคริปต์เพราะข้อผิดพลาดมีแนวโน้มที่จะมี glob ส่งกลับบางสิ่งบางอย่างเมื่อไม่มีการแข่งขัน ดังนั้นหนึ่งสามารถตั้งค่าตัวเลือก nullglob ที่จุดเริ่มต้นของสคริปต์และจะไม่จำเป็นในฟังก์ชั่น โปรดระลึกไว้เสมอ: เราต้องการโซลูชันที่ใช้งานได้กับตัวเลือก nullglob
คำเตือน:
หากเราไม่มีสิทธิ์เข้าถึงแบบอ่านไปยังไดเรกทอรีฟังก์ชันจะรายงานเหมือนกับว่ามีไดเรกทอรีว่างเปล่า สิ่งนี้ใช้กับฟังก์ชันที่แสดงรายการไดเรกทอรีและ grep ผลลัพธ์ด้วย
shopt -s nullglob
คำสั่งไม่ได้มาตรฐาน POSIX
มันใช้ subshell $()
ที่สร้างขึ้นโดย มันไม่ใช่เรื่องใหญ่ แต่มันก็ดีถ้าเราสามารถหลีกเลี่ยงได้
มือโปร:
ไม่ใช่ว่ามันจะสำคัญ แต่ฟังก์ชั่นนี้เร็วกว่าฟังก์ชั่นก่อนหน้านี้ถึงสี่เท่าโดยวัดจากจำนวนเวลา CPU ที่ใช้ในเคอร์เนลภายในกระบวนการ
โซลูชั่นอื่น ๆ :
เราสามารถเอา POSIX ไม่ใช่shopt -s nullglob
คำสั่งบน LHS และวางสาย"$1/* $1/.[^.]* $1/..?*"
ใน RHS และกำจัดแยกบวกเท็จที่เกิดขึ้นเมื่อเรามีเพียงชื่อไฟล์'*'
, .[^.]*
หรือ..?*
ในไดเรกทอรี:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
หากไม่มีshopt -s nullglob
คำสั่งตอนนี้ก็เหมาะสมแล้วที่จะลบ subshell แต่เราต้องระวังเพราะเราต้องการหลีกเลี่ยงการแยกคำและยังอนุญาตให้มีการขยายแบบกลมบน LHS โดยเฉพาะอย่างยิ่งการอ้างเพื่อหลีกเลี่ยงการแยกคำไม่ทำงานเพราะมันยังช่วยป้องกันการขยายตัวของ glob ทางออกของเราคือพิจารณา globs แยก:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
เรายังคงมีการแบ่งคำสำหรับแต่ละ glob แต่มันก็โอเคตอนนี้เพราะมันจะส่งผลให้เกิดข้อผิดพลาดเมื่อไดเรกทอรีไม่ว่างเปล่า เราได้เพิ่ม 2> / dev / null เพื่อทิ้งข้อความแสดงข้อผิดพลาดเมื่อมีไฟล์จำนวนมากที่ตรงกับ glob ที่กำหนดบน LHS
เราจำได้ว่าเราต้องการโซลูชันที่ทำงานกับตัวเลือก nullglob เช่นกัน การแก้ปัญหาข้างต้นล้มเหลวด้วยตัวเลือก nullglob เพราะเมื่อไดเรกทอรีว่างเปล่า LHS ก็ว่างเปล่า โชคดีที่มันไม่เคยบอกว่าไดเรกทอรีว่างเปล่าเมื่อไม่มี มันล้มเหลวที่จะบอกว่ามันว่างเปล่าเมื่อมันเป็น ดังนั้นเราสามารถจัดการตัวเลือก nullglob แยกกัน เราไม่สามารถเพิ่มเคส[ "$1/"* = "" ]
และอื่น ๆ ได้เพราะสิ่งเหล่านี้จะขยายเป็น[ = "" ]
ฯลฯ ซึ่งไม่ถูกต้องทางไวยากรณ์ ดังนั้นเราจึงใช้ [ "$1/"* "" = "" ]
ฯลฯ แทน เราอีกครั้งจะต้องพิจารณากรณีที่สาม*
, ..?*
และ.[^.]*
เพื่อให้ตรงกับไฟล์ที่ซ่อนอยู่ แต่ไม่ได้.
และ..
. สิ่งเหล่านี้จะไม่รบกวนถ้าเราไม่มีตัวเลือก nullglob เพราะพวกเขายังไม่เคยพูดว่ามันว่างเปล่าเมื่อไม่มี ดังนั้นทางออกที่เสนอสุดท้ายคือ:
function emptydir {
[ "$1/"* "" = "" ] 2> /dev/null &&
[ "$1/"..?* "" = "" ] 2> /dev/null &&
[ "$1/".[^.]* "" = "" ] 2> /dev/null ||
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
ความกังวลด้านความปลอดภัย:
สร้างสองไฟล์rm
และx
ในไดเรกทอรีว่างและดำเนินการ*
ในการแจ้งให้ glob *
จะขยายไปและจะต้องดำเนินการเพื่อลบrm x
x
นี้ไม่ได้เป็นความกังวลด้านความปลอดภัยในการทำงานเพราะเรา globs จะอยู่ที่การขยายตัวจะไม่ได้มองว่าเป็นคำสั่ง for f in *
แต่เป็นข้อโต้แย้งเช่นเดียวกับใน