วิธีละเว้นคำสั่ง xargs หากอินพุต stdin ว่างเปล่า?


189

พิจารณาคำสั่งนี้:

ls /mydir/*.txt | xargs chown root

ความตั้งใจที่จะเปลี่ยนเจ้าของไฟล์ข้อความทั้งหมดmydirเป็นรูท

ปัญหาคือว่าถ้าไม่มี.txtไฟล์ในmydirxargs แล้ว thows ข้อผิดพลาดบอกว่าไม่มีเส้นทางที่ระบุ นี่เป็นตัวอย่างที่ไม่เป็นอันตรายเนื่องจากมีข้อผิดพลาดเกิดขึ้น แต่ในบางกรณีเช่นในสคริปต์ที่ฉันต้องใช้ที่นี่พา ธ ว่างจะถือว่าเป็นไดเรกทอรีปัจจุบัน ดังนั้นถ้าฉันเรียกใช้คำสั่ง/home/tom/นั้นถ้าไม่มีผลลัพธ์ls /mydir/*.txtและไฟล์ทั้งหมดภายใต้/home/tom/มีเจ้าของเปลี่ยนเป็นรูต

ดังนั้นฉันจะให้ xargs เพิกเฉยกับผลลัพธ์ที่ว่างเปล่าได้อย่างไร


6
ด้านข้าง: ห้ามส่งออกไปป์ไลน์จากlsการใช้งานแบบเป็นโปรแกรม ดูmywiki.wooledge.org/ParsingLs
Charles Duffy

กรณีการใช้งานของฉันคือgit branch --merged | grep -v '^* ' | xargs git branch -dซึ่งยังล้มเหลวในการป้อนข้อมูลที่ว่างเปล่า
belkka

คำตอบ:


305

สำหรับ GNU xargsคุณสามารถใช้-rหรือ--no-run-if-emptyตัวเลือก:

--no-run-if-empty
-r
หากอินพุตมาตรฐานไม่มีช่องว่างใด ๆ ห้ามรันคำสั่ง โดยปกติคำสั่งจะรันหนึ่งครั้งแม้ว่าจะไม่มีอินพุต ตัวเลือกนี้เป็นส่วนขยายของ GNU


9
ฉันค้นหาด้วยกูเกิ้ลเป็นครั้งแรกและพบสิ่งนี้ (แต่จะไม่ถามถ้าไม่อ่านคู่มือ) ฉันคิดว่านี่น่าจะเป็นพฤติกรรมเริ่มต้นโดยไม่ต้องใช้สวิตช์เพื่อเปิดใช้งาน
JonnyJD

28
น่าเสียดายที่นี่ใช้ไม่ได้กับ Mac ทั้งตัวเลือกแบบสั้นและแบบยาว
พุธที่

9
@wedi - OSX มีพื้นที่ผู้ใช้ BSD ดังนั้นสิ่งนี้จะเกิดขึ้นบ่อยครั้ง คุณสามารถแก้ไขได้โดยใช้ homebrew เพื่อติดตั้งไฟล์ GNU
edk750

7
brew install findutilsคุณหมายถึง ไม่findutils fileutils
Mitar

3
บน macOS ไม่ได้ให้ผลลัพธ์การตั้งค่าสถานะในการไม่เรียกใช้คำสั่งต่อไปดังนั้นฉันเดาว่ามันไม่จำเป็น
Trejkaz

15

ผู้ใช้ xargs ไม่ใช่ GNU อาจใช้ประโยชน์จาก-L <#lines>, -n <#args>, -iและ-I <string>:

ls /empty_dir/ | xargs -n10 chown root # chown executed every 10 args or fewer
ls /empty_dir/ | xargs -L10 chown root # chown executed every 10 lines or fewer
ls /empty_dir/ | xargs -i cp {} {}.bak # every {} is replaced with the args from one input line
ls /empty_dir/ | xargs -I ARG cp ARG ARG.bak # like -i, with a user-specified placeholder

โปรดทราบว่า xargs แยกบรรทัดที่ช่องว่าง แต่มีการอ้างอิงและการหลบหนี RTFM สำหรับรายละเอียด

นอกจากนี้ตามที่ Doron Behar กล่าวถึงวิธีแก้ปัญหานี้ไม่สามารถพกพาได้ดังนั้นจึงจำเป็นต้องมีการตรวจสอบ:

$ uname -is
SunOS sun4v
$ xargs -n1 echo blah < /dev/null
$ uname -is
Linux x86_64
$ xargs --version | head -1
xargs (GNU findutils) 4.7.0-git
$ xargs -n1 echo blah < /dev/null
blah

2
ดูเหมือนจะไม่ตอบคำถามหรือไม่
Nicolas Raoul

2
@Nicolas: เป็นวิธีการป้องกันการดำเนินการหากเวอร์ชันของคุณxargsไม่รองรับ--no-run-if-emptyสวิตช์และพยายามเรียกใช้อาร์กิวเมนต์คำสั่งที่ไม่มีอินพุต STDIN (นี่เป็นกรณีใน Solaris 10) เวอร์ชันในยูนิกซ์อื่นอาจเพิกเฉยกับรายการว่างเปล่า (เช่น AIX)
arielCo

4
ใช่ บน MAC OSX echo '' | xargs -n1 echo blahไม่ทำอะไรเลย ในขณะที่echo 'x' | xargs -n1 echo blahพิมพ์ "blah"
carlosayam

2
สำหรับการสนับสนุน GNU และ BSD / OSX ฉันลงเอยด้วยการใช้สิ่งที่ชอบ: ls /mydir/*.txt | xargs -n 1 -I {} chown root {}ตามคำแนะนำนี้
Luís Bianchin

3
มันควรจะตั้งข้อสังเกตว่าecho '' | xargs -n1 echo blahการพิมพ์blahกับ xargsGNU
Doron Behar

11

man xargs--no-run-if-emptyกล่าวว่า


1
หากคุณอยู่บน OSX มันจะไม่ทำงาน edk750 ได้อธิบายสิ่งนี้ในความคิดเห็นของพวกเขาหากคุณสงสัยว่าทำไม
jasonleonhard

9

ในแง่ของxargsคุณสามารถใช้-rเป็นข้อเสนอแนะ แต่ก็ไม่ได้รับการสนับสนุนโดย xargsBSD

เพื่อเป็นการหลีกเลี่ยงปัญหาคุณอาจส่งไฟล์ชั่วคราวบางไฟล์เพิ่มเติมเช่น:

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root $(mktemp)

หรือเปลี่ยนเส้นทาง stderr ไปเป็น null ( 2> /dev/null) เช่น

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root 2> /dev/null || true

อีกวิธีที่ดีกว่าคือวนซ้ำไฟล์ที่พบโดยใช้การwhileวนซ้ำ:

find /mydir -type f -name "*.txt" -print0 | while IFS= read -r -d '' file; do
  chown -v root "$file"
done

ดูเพิ่มเติมที่: ละเว้นผลลัพธ์ว่างสำหรับ xargs ใน Mac OS X


นอกจากนี้โปรดทราบว่าวิธีการเปลี่ยนการอนุญาตของคุณไม่ได้ยอดเยี่ยมและเป็นการลดทอน แน่นอนคุณไม่ควรแยกวิเคราะห์lsคำสั่ง (ดู: ทำไมคุณไม่ควรแยกผลลัพธ์ของคำสั่ง ls ) โดยเฉพาะอย่างยิ่งเมื่อคุณเรียกใช้คำสั่งของคุณโดยรูทเนื่องจากไฟล์ของคุณอาจมีอักขระพิเศษซึ่งอาจถูกตีความโดยเชลล์หรือจินตนาการไฟล์ที่มีอักขระเว้นวรรคอยู่รอบ ๆ/ผลลัพธ์จะแย่มาก

ดังนั้นคุณควรเปลี่ยนวิธีการและใช้findคำสั่งแทนเช่น

find /mydir -type f -name "*.txt" -execdir chown root {} ';'

1
ขออภัยฉันไม่เข้าใจวิธีการที่while IFS= read -r -d '' fileถูกต้อง คุณสามารถอธิบาย?
เอเดรีย

2

บน OSX: การใช้งานทุบตีใหม่ของxargsการจัดการกับ-rข้อโต้แย้งใส่ที่เช่นใน$HOME/binและเพิ่มไปที่PATH:

#!/bin/bash
stdin=$(cat <&0)
if [[ $1 == "-r" ]] || [[ $1 == "--no-run-if-empty" ]]
then
    # shift the arguments to get rid of the "-r" that is not valid on OSX
    shift
    # wc -l return some whitespaces, let's get rid of them with tr
    linecount=$(echo $stdin | grep -v "^$" | wc -l | tr -d '[:space:]') 
    if [ "x$linecount" = "x0" ]
    then
      exit 0
    fi
fi

# grep returns an error code for no matching lines, so only activate error checks from here
set -e
set -o pipefail
echo $stdin | /usr/bin/xargs $@

2

นี่คือพฤติกรรมของ GNU xargs ซึ่งสามารถถูกยกเลิกได้โดยใช้ -r, - no-run-if-empty

ตัวแปร * BSD ของ xargs มีพฤติกรรมนี้โดยปริยายดังนั้นจึงไม่จำเป็นต้องใช้ -r ตั้งแต่ FreeBSD 7.1 (เผยแพร่ในเดือนมกราคม 2552) อาร์กิวเมนต์ -r ได้รับการยอมรับ (แม่มดไม่ทำอะไรเลย) ด้วยเหตุผลที่เข้ากันได้

ฉันชอบใช้ longopts ในสคริปต์ส่วนตัว แต่เนื่องจาก * BSD xargs ไม่ได้ใช้ longopts เพียงใช้ "-r" และ xargs จะทำหน้าที่เหมือนกันใน * BSD และบนระบบ linux

xargs บน MacOS (ปัจจุบัน MacOS Mojave) เศร้าไม่สนับสนุนอาร์กิวเมนต์ "-r"

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