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


159

บอกว่าฉันมีไฟล์ที่foo.txtระบุNข้อโต้แย้ง

arg1
arg2
...
argN

ซึ่งฉันต้องผ่านไปยังคำสั่ง my_command

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


11
"อาร์กิวเมนต์" พหูพจน์หรือ "อาร์กิวเมนต์" เดี่ยวหรือไม่ คำตอบที่ได้รับการยอมรับนั้นถูกต้องเฉพาะในกรณีที่มีการโต้แย้งเพียงครั้งเดียว - ซึ่งเป็นสิ่งที่เนื้อความสอบถามเกี่ยวกับ - แต่ไม่ได้อยู่ในกรณีที่มีหลายอาร์กิวเมนต์ซึ่งหัวข้อจะปรากฏขึ้นเพื่อสอบถาม
Charles Duffy

คำตอบ 4 ข้อด้านล่างมักจะมีผลลัพธ์เหมือนกัน แต่ก็มีความหมายที่แตกต่างกันเล็กน้อย ตอนนี้ฉันจำได้ว่าทำไมฉันถึงหยุดเขียนสคริปต์ทุบตี: P
Navin

คำตอบ:


237

หากเชลล์ของคุณทุบตี (ท่ามกลางคนอื่น) ทางลัดสำหรับ$(cat afile)คือ$(< afile)ดังนั้นคุณจะเขียน:

mycommand "$(< file.txt)"

เอกสารในหน้า bash man ในส่วน 'การทดแทนคำสั่ง'

เปลี่ยนคำสั่งของคุณจาก stdin ดังนั้น: mycommand < file.txt


11
เพื่อเป็นคนคล่องแคล่วมันไม่ใช่ทางลัดในการใช้<โอเปอเรเตอร์ หมายความว่าเชลล์เองจะทำการเปลี่ยนเส้นทางแทนที่จะดำเนินการcatไบนารีสำหรับคุณสมบัติการเปลี่ยนเส้นทาง
lstyls

2
เป็นการตั้งค่าเคอร์เนลดังนั้นคุณจะต้องคอมไพล์เคอร์เนลอีกครั้ง หรือตรวจสอบคำสั่ง xargs
glenn jackman

3
@ykaner เพราะไม่มี"$(…)"เนื้อหาของfile.txtจะถูกส่งผ่านไปยังอินพุตมาตรฐานของmycommandไม่ใช่อาร์กิวเมนต์ "$(…)"วิธีการเรียกใช้คำสั่งและให้กลับออกเป็นสตริง ; ที่นี่“ คำสั่ง” เท่านั้นอ่านไฟล์ แต่มันอาจจะซับซ้อนมากขึ้น
törzsmókus

3
มันไม่ทำงานสำหรับฉันถ้าฉันทำเครื่องหมายคำพูดหาย ด้วยเครื่องหมายคำพูดจะใช้ไฟล์ทั้งหมดเป็น 1 อาร์กิวเมนต์ หากไม่มีเครื่องหมายอัญประกาศมันจะตีความแต่ละบรรทัดเป็น ARG แยกกัน
Pete

1
@ LarsH คุณถูกต้องอย่างแน่นอน นั่นเป็นวิธีที่ทำให้สับสนน้อยลงขอบคุณ
lstyls

37

ดังกล่าวแล้วคุณสามารถใช้ backticks $(cat filename)หรือ

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

a "b c" d

ข้อโต้แย้งที่คุณจะได้รับคือ:

a
"b
c"
d

หากคุณต้องการดึงแต่ละบรรทัดเป็นอาร์กิวเมนต์ให้ใช้ while / read / do construct:

while read i ; do command_name $i ; done < filename

ฉันควรได้กล่าวถึงฉันสมมติว่าคุณกำลังใช้ทุบตี ฉันรู้ว่ามีกระสุนอื่น ๆ อยู่ที่นั่น แต่เกือบทุกเครื่อง * ที่ฉันได้ทำงานด้วยไม่ว่าจะเป็นทุบตีหรือเทียบเท่า IIRC ไวยากรณ์นี้ควรใช้งานได้กับ ksh และ zsh
จะ

1
ควรอ่านread -rถ้าคุณไม่ต้องการขยายลำดับแบ็กสแลช - เอสเคปและ NUL เป็นตัวคั่นที่ปลอดภัยกว่าการใช้งานขึ้นบรรทัดใหม่โดยเฉพาะอย่างยิ่งถ้าอาร์กิวเมนต์ที่คุณส่งผ่านนั้นเป็นชื่อไฟล์ซึ่งอาจมีบรรทัดใหม่ นอกจากนี้โดยไม่ต้องล้าง IFS iคุณจะได้รับชั้นนำและต่อท้ายช่องว่างเคลียร์โดยปริยายจาก
Charles Duffy

18
command `< file`

จะส่งเนื้อหาของไฟล์ไปยังคำสั่งบน stdin แต่จะตัดการขึ้นบรรทัดใหม่ซึ่งหมายความว่าคุณไม่สามารถทำซ้ำแต่ละบรรทัดได้ เพื่อที่คุณจะสามารถเขียนสคริปต์ด้วย 'for' loop:

for line in `cat input_file`; do some_command "$line"; done

หรือ (ตัวแปรหลายบรรทัด):

for line in `cat input_file`
do
    some_command "$line"
done

หรือ (ตัวแปรหลายบรรทัดพร้อม$()แทน``):

for line in $(cat input_file)
do
    some_command "$line"
done

อ้างอิง:

  1. สำหรับไวยากรณ์ลูป: https://www.cyberciti.biz/faq/bash-for-loop/

นอกจากนี้การใส่< filebackticks หมายความว่ามันไม่ได้ทำการเปลี่ยนเส้นทางcommandเลย
ชาร์ลส์ดัฟฟี่

3
(คนที่ยกระดับการทดสอบนี้จริงหรือcommand `< file` ไม่ไม่ทำงานใน bash หรือ POSIX sh; zsh เป็นเรื่องที่แตกต่างกัน
Charles Duffy

@CharlesDuffy คุณมีความเฉพาะเจาะจงมากขึ้นเกี่ยวกับวิธีการที่ไม่ทำงานหรือไม่?
mwfearnley

@CharlesDuffy สิ่งนี้ใช้ได้ใน bash 3.2 และ zsh 5.3 สิ่งนี้จะไม่ทำงานใน sh, ash และ dash แต่ไม่ใช่ $ (<file)
Arnie97

1
@mwfearnley ยินดีที่จะให้ความเฉพาะเจาะจงนั้น เป้าหมายคือทำซ้ำมากกว่าบรรทัดใช่ไหม ใส่Hello Worldหนึ่งบรรทัด คุณจะเห็นมันทำงานครั้งแรกsome_command Helloแล้วsome_command World, ไม่ได้ หรือsome_command "Hello World" some_command Hello World
Charles Duffy

15

คุณทำเช่นนั้นโดยใช้ backticks:

echo World > file.txt
echo Hello `cat file.txt`

4
สิ่งนี้ไม่ได้สร้างการโต้แย้ง - เพราะมันไม่ได้อ้างถึงมันเป็นเรื่องของการแยกสตริงดังนั้นถ้าคุณปล่อยออกมาecho "Hello * Starry * World" > file.txtในขั้นตอนแรกคุณจะได้รับอาร์กิวเมนต์อย่างน้อยสี่แยกผ่านไปยังคำสั่งที่สอง - และมีแนวโน้ม เพิ่มเติมตามที่*s จะขยายไปยังชื่อของไฟล์ที่มีอยู่ในไดเรกทอรีปัจจุบัน
Charles Duffy

3
... และเพราะมันทำงานขยายตัว glob ก็ไม่ได้ปล่อยออกมาว่าสิ่งที่อยู่ในแฟ้ม และเนื่องจากมันทำการดำเนินการแทนคำสั่งจึงไม่มีประสิทธิภาพอย่างยิ่ง - จริง ๆ แล้วมันfork()ปิด subshell ด้วย FIFO ที่แนบมากับ stdout แล้วเรียกใช้/bin/catเป็นลูกของ subshell นั้นจากนั้นอ่านเอาต์พุตผ่าน FIFO; เปรียบเทียบกับ$(<file.txt)ซึ่งอ่านเนื้อหาของไฟล์เป็นการทุบตีโดยตรงโดยไม่ต้องมี subshells หรือ FIFO ที่เกี่ยวข้อง
Charles Duffy

11

หากคุณต้องการทำสิ่งนี้ในวิธีที่มีประสิทธิภาพซึ่งใช้ได้กับทุกอาร์กิวเมนต์บรรทัดคำสั่งที่เป็นไปได้ (ค่าที่มีช่องว่างค่าที่มีการขึ้นบรรทัดใหม่ค่าที่มีอักขระเครื่องหมายคำพูดตัวอักษรค่าที่พิมพ์ไม่ได้ค่าที่มีตัวอักษรกลม ฯลฯ ) น่าสนใจมากขึ้น.


ในการเขียนไฟล์ให้กำหนดอาเรย์ของอาร์กิวเมนต์:

printf '%s\0' "${arguments[@]}" >file

... เปลี่ยน"argument one", "argument two"ฯลฯ ตามความเหมาะสม


หากต้องการอ่านจากไฟล์นั้นและใช้เนื้อหา (ใน bash, ksh93 หรือเชลล์ล่าสุดที่มีอาร์เรย์):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

หากต้องการอ่านจากไฟล์นั้นและใช้เนื้อหาของมัน (ในเชลล์ที่ไม่มีอาร์เรย์โปรดทราบว่าสิ่งนี้จะเขียนทับรายการอาร์กิวเมนต์บรรทัดคำสั่งในเครื่องของคุณและทำได้ดีที่สุดในฟังก์ชั่นเช่นคุณเขียนทับอาร์กิวเมนต์ของฟังก์ชัน รายการทั่วโลก):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

โปรดทราบว่า-d(การอนุญาตให้ใช้ตัวคั่นจุดสิ้นสุดบรรทัดอื่น) เป็นส่วนขยายที่ไม่ใช่ POSIX และเชลล์ที่ไม่มีอาร์เรย์อาจไม่สนับสนุน ในกรณีนี้คุณอาจต้องใช้ภาษาที่ไม่ใช่เชลล์เพื่อแปลงเนื้อหาที่คั่นด้วย NUL ให้อยู่ในevalรูปแบบที่ปลอดภัย:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"

6

นี่คือวิธีที่ฉันส่งเนื้อหาของไฟล์เป็นอาร์กิวเมนต์ไปยังคำสั่ง:

./foo --bar "$(cat ./bar.txt)"

1
นี่คือ - แต่มีประสิทธิภาพน้อยกว่ามาก - เทียบเท่าได้อย่างมีประสิทธิภาพ$(<bar.txt)(เมื่อใช้เชลล์เช่นทุบตีด้วยส่วนขยายที่เหมาะสม) $(<foo)เป็นกรณีพิเศษ: ซึ่งแตกต่างจากการทดแทนคำสั่งปกติตามที่ใช้ใน$(cat ...)มันไม่แยกออก subshell เพื่อทำงานในและจึงหลีกเลี่ยงการใช้ค่าใช้จ่ายมาก
Charles Duffy

4

หากสิ่งที่คุณต้องทำคือเปิดไฟล์arguments.txtด้วยเนื้อหา

arg1
arg2
argN

ลงไปmy_command arg1 arg2 argNแล้วคุณสามารถใช้xargs:

xargs -a arguments.txt my_command

คุณสามารถใส่อาร์กิวเมนต์เพิ่มเติมแบบคงที่ในการxargsโทรเช่นxargs -a arguments.txt my_command staticArgที่จะโทรmy_command staticArg arg1 arg2 argN


1

ในเปลือกทุบตีของฉันต่อไปนี้ทำงานเหมือนมีเสน่ห์:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

โดยที่ input_file คือ

arg1
arg2
arg3

ในฐานะที่เป็นที่เห็นได้ชัดนี้จะช่วยให้คุณสามารถรันคำสั่งหลายกับแต่ละบรรทัดจาก input_file, เคล็ดลับเล็ก ๆ น้อย ๆ ที่ดีผมได้เรียนรู้ที่นี่


3
"เคล็ดลับเล็ก ๆ น้อย ๆ ที่ดี" ของคุณนั้นอันตรายจากมุมมองด้านความปลอดภัยหากคุณมีข้อโต้แย้งที่มีอยู่$(somecommand)คุณจะได้รับคำสั่งนั้นแทนที่จะถูกส่งผ่านเป็นข้อความ ในทำนองเดียวกัน>/etc/passwdจะถูกประมวลผลเป็นการเปลี่ยนเส้นทางและเขียนทับ/etc/passwd(หากทำงานด้วยสิทธิ์ที่เหมาะสม) และอื่น ๆ
Charles Duffy

2
การทำสิ่งต่อไปนี้ปลอดภัยกว่าแทน (ในระบบที่มีส่วนขยาย GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _และมีประสิทธิภาพมากกว่าเนื่องจากส่งผ่านอาร์กิวเมนต์จำนวนมากไปยังแต่ละเชลล์มากที่สุดเท่าที่จะทำได้แทนที่จะเริ่มต้นหนึ่งเชลล์ต่อบรรทัดในไฟล์อินพุตของคุณ
Charles Duffy

และแน่นอนสูญเสียการใช้งานที่ไม่มีประโยชน์ของcat
tripleee

0

หลังจากแก้ไขคำตอบของ @Wesley Riceสองครั้งฉันตัดสินใจว่าการเปลี่ยนแปลงของฉันใหญ่เกินไปที่จะเปลี่ยนคำตอบของเขาต่อไปแทนที่จะเขียนเอง ฉันเลยตัดสินใจเขียนเอง!

อ่านแต่ละบรรทัดของไฟล์ในและดำเนินการกับมันทีละบรรทัดเช่นนี้:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

นี้มาโดยตรงจากผู้เขียนวิเวก Gite ที่นี่: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ เขาได้รับเครดิต!

ไวยากรณ์: อ่านบรรทัดไฟล์ต่อบรรทัดบนเชลล์ Bash Unix & Linux:
1. ไวยากรณ์เป็นดังนี้สำหรับ bash, ksh, zsh และเชลล์อื่น ๆ ทั้งหมดเพื่ออ่านไฟล์บรรทัดต่อบรรทัด
2 while read -r line; do COMMAND; done < input.file
3. -rตัวเลือกที่ส่งไปยังคำสั่งอ่าน ป้องกัน backslash escapes จากการตีความ
4. เพิ่มIFS=ตัวเลือกก่อนที่จะอ่านคำสั่งเพื่อป้องกันช่องว่างนำหน้า / ต่อท้ายไม่ให้ถูกตัด -
5while IFS= read -r line; do COMMAND_on $line; done < input.file


และตอนนี้เพื่อตอบคำถามที่ปิดไปแล้วซึ่งฉันมี: เป็นไปได้หรือไม่ที่จะ "git เพิ่ม" รายชื่อไฟล์จากไฟล์? - นี่คือคำตอบของฉัน:

ทราบว่าFILES_STAGEDเป็นตัวแปรที่มีเส้นทางสัมบูรณ์ไปยังไฟล์ที่มีพวงของสายซึ่งแต่ละสายเป็นเส้นทางเทียบกับไฟล์ที่ผมอยากจะทำที่git addบน ข้อมูลโค้ดนี้กำลังจะเป็นส่วนหนึ่งของไฟล์"eRCaGuy_dotfiles / helpful_scripts / sync_git_repo_to_build_machine.sh"ในโครงการนี้เพื่อเปิดใช้งานการซิงค์ไฟล์ที่ง่ายจากพีซีเครื่องหนึ่ง (เช่นคอมพิวเตอร์ที่ฉันใช้รหัส) เพิ่มเติมคอมพิวเตอร์ที่มีประสิทธิภาพในการสร้างผม): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

อ้างอิง:

  1. ที่ฉันคัดลอกคำตอบของฉันจาก: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. สำหรับไวยากรณ์ลูป: https://www.cyberciti.biz/faq/bash-for-loop/

ที่เกี่ยวข้อง:

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