เผยแพร่ข้อโต้แย้งทั้งหมดใน bash shell script


851

ฉันกำลังเขียนสคริปต์ง่าย ๆ ที่เรียกสคริปต์อื่นและฉันต้องการเผยแพร่พารามิเตอร์จากสคริปต์ปัจจุบันของฉันไปยังสคริปต์ที่ฉันกำลังดำเนินการ

ตัวอย่างเช่นชื่อสคริปต์ของฉันคือfoo.shและการโทรbar.sh

foo.sh:

bar $1 $2 $3 $4

ฉันจะทำสิ่งนี้โดยไม่ระบุพารามิเตอร์อย่างชัดเจนได้อย่างไร



คำตอบ:


1405

ใช้"$@"แทนการเรียบ$@ถ้าคุณต้องการพารามิเตอร์ของคุณจะถูกส่งผ่านเหมือนกัน

สังเกต:

$ cat foo.sh
#!/bin/bash
baz.sh $@

$ cat bar.sh
#!/bin/bash
baz.sh "$@"

$ cat baz.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4

$ ./foo.sh first second
Received: first
Received: second
Received:
Received:

$ ./foo.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:

$ ./bar.sh first second
Received: first
Received: second
Received:
Received:

$ ./bar.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:

7
วิธีนี้ใช้งานได้กับการส่งผ่านสตริงที่อ้างถึง / ใช้งานจริง: สังเกต: cat rsync_foo.sh #! / bin / bash echo "$ @" rsync "$ @" ./rsync_foo.sh -n "bar me" bar2 bar me ไดเรกทอรี bar2skipping bar me เป็นไปได้ไหมที่มีสคริปต์เชลล์ที่สามารถเห็นบรรทัดคำสั่งดั้งเดิมที่แท้จริงพร้อมด้วยเครื่องหมายคำพูดหรือสตริงที่ใช้ Escape
Mark Edington

3
สิ่งที่เกี่ยวกับการผ่านธง เช่น './bar.sh - กับสิ่งของ'
Nathan Long

4
ฉันขอแนะนำให้ทุกคนที่ต้องการเข้าใจเรื่องของการแยกคำได้ดียิ่งขึ้นเพื่ออ่านเพิ่มเติมได้ที่นี่
Rany Albeg Wein

4
ฉันขอแนะนำให้คุณแสดงสิ่งที่เกิดขึ้นเมื่อรวมตัวอย่าง'arg with spaces'สำหรับสามตัวอย่างด้วย ฉันรู้สึกประหลาดใจกับผลลัพธ์ที่ได้ หวังว่าคุณจะสามารถอธิบายได้
Michael Scheper

2
@MichaelScheper แต่อย่างใด./foo.sh "arg with spaces"และ./foo.sh 'arg with spaces'เหมือนกัน 100% ดังนั้นฉันไม่เห็นว่าคำแนะนำในการเพิ่มลงในตัวอย่างที่ให้ไว้จะเป็นความช่วยเหลือใด ๆ
Charles Duffy

475

สำหรับทุบตีและกระสุนคล้ายบอร์นอื่น ๆ :

java com.myserver.Program "$@"

13
@Amir: ไม่มีไม่ได้สำหรับcsh สำหรับสุขภาพจิตของทุกคน: ไม่ csh แต่ฉันคิดว่าอาจ$argv:qจะใช้ได้กับ csh บางรุ่น
Chris Johnsen

2
ขอบคุณ! ตามที่ต้องการสิ่งนี้จะผ่านชุดของการขัดแย้งที่สคริปต์ได้รับไม่ใช่การโต้แย้งใหญ่ ๆ จำเป็นต้องใช้เครื่องหมายคำพูดคู่ ทำงานได้แม้ว่าจะมีอาร์กิวเมนต์ที่ยกมาซึ่งมีช่องว่าง
แอนดี้โทมัส

24
ที่เกี่ยวข้อง: หากเชลล์สคริปต์ของคุณทำหน้าที่เป็น wrapper ในการรันจาวาให้ลองสร้างบรรทัดสุดท้ายexec java com.myserver.Program "$@"ซึ่งจะทำให้ bash ทำการเอ็กซีคิวต์เป็นจาวาแทนที่จะรอให้เสร็จสมบูรณ์ ดังนั้นคุณใช้สล็อตกระบวนการที่น้อยลงหนึ่งช่อง นอกจากนี้หากกระบวนการหลัก (ซึ่งเรียกใช้สคริปต์ของคุณ) ดูผ่าน pid และคาดว่าจะเป็นกระบวนการ 'java' สิ่งผิดปกติบางอย่างอาจผิดพลาดได้หากคุณไม่ทำ exec exec ทำให้ java ที่จะสืบทอด pid เดียวกัน
greggo

7
@dragonxlwang: คุณสามารถใช้ตัวแปรอาเรย์ถ้าเชลล์ของคุณรองรับพวกมัน (เช่นbash , zsh , อื่น ๆ แต่ไม่ใช่บอร์นธรรมดาหรือเชลล์ POSIX-shell): บันทึกเป็นargsด้วยargs=("$@")และขยายแต่ละองค์ประกอบเป็นเชลล์ที่แยกจากกัน "word" ( คล้ายกับ"$@") "${args[@]}"ด้วย
Chris Johnsen

1
มี "gotchas" ที่จะต้องจำไว้เมื่อใช้"$@"เช่นมันจะล้มเหลวถ้าคุณหนีช่องว่างในการโต้แย้งหรือตัวละครโมฆะหรือตัวละครพิเศษอื่น ๆ ?
IQAndreas

97

ใช้"$@"(ใช้ได้กับPOSIX ที่เข้ากันได้ทั้งหมด)

[... ], bash มีตัวแปร "$ @" ซึ่งขยายไปยังพารามิเตอร์บรรทัดคำสั่งทั้งหมดคั่นด้วยช่องว่าง

จากทุบตีโดยยกตัวอย่างเช่น


6
ดังนั้น "$ @" ไม่ใช่แค่อัญประกาศประมาณ $ @ แต่โดยความแตกต่างของตัวแปรในตัว
Ben

5
@ Ben มันเป็นตัวแปรเดียว แต่มันต้องใช้คำพูดสองรอบ ๆ มันจะมีค่ามีประโยชน์แตกต่างจาก $*(หัก) ฉันเชื่อว่ามีความก้าวหน้าทางประวัติศาสตร์ที่นี่; $*ไม่ทำงานตามที่ออกแบบมาดังนั้นจึง$@ถูกคิดค้นขึ้นมาเพื่อแทนที่ แต่กฎการอ้างถึงเป็นสิ่งที่พวกเขาเป็นคู่ราคาอยู่รอบ ๆ มันยังคงจำเป็น (หรือมันจะกลับไปสู่$*ความหมายที่แตกสลาย)
tripleee

7
"ดังนั้น" $ @ "ไม่ใช่แค่อัญประกาศประมาณ $ @ แต่จะมีตัวแปรในตัวที่แตกต่างออกไปหรือไม่" - สำหรับทุกเจตนาและวัตถุประสงค์ใช่: stackoverflow.com/a/28099707/162094
Stuart Berg

1
หากคุณเรียกใช้สคริปต์ที่มีecho "$@"ตาม./script.sh a "b c" dนั้นคุณจะได้รับa b c dแทนa "b c" dซึ่งแตกต่างกันมาก
isarandi

7
@isarandi ในขณะที่มันเป็นความจริงที่เอาท์พุทไม่ได้มีคำพูดอีกต่อไปนั่นคือสิ่งที่คุณคาดหวัง ... แต่มั่นใจได้ว่าภายในสคริปต์echoได้รับสามข้อโต้แย้ง: "a" "b c" "d"(จากนั้นเชลล์เข้าด้วยกันเป็นส่วนหนึ่งของการขยายสตริง) แต่ถ้าคุณต้องการใช้คุณจะได้รับfor i in "$@"; do echo $i; done a⏎b c⏎d
FeRD

64

ฉันรู้ว่านี่ได้รับคำตอบที่ดี แต่นี่เป็นการเปรียบเทียบระหว่าง "$ @" $ @ "$ *" และ $ *

เนื้อหาของสคริปต์ทดสอบ:

# cat ./test.sh
#!/usr/bin/env bash
echo "================================="

echo "Quoted DOLLAR-AT"
for ARG in "$@"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-AT"
for ARG in $@; do
    echo $ARG
done

echo "================================="

echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
    echo $ARG
done

echo "================================="

ตอนนี้รันสคริปต์ทดสอบด้วยอาร์กิวเมนต์ต่าง ๆ :

# ./test.sh  "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================

1
คำอธิบายสั้น ๆ ที่ดีและสนับสนุนคำตอบนี้
เมอร์ลิน

33
#!/usr/bin/env bash
while [ "$1" != "" ]; do
  echo "Received: ${1}" && shift;
done;

แค่คิดว่านี่อาจมีประโยชน์มากกว่านี้เล็กน้อยเมื่อพยายามทดสอบว่า args เข้ามาในสคริปต์ของคุณอย่างไร


6
สิ่งนี้ไม่ได้ช่วยตอบคำถามของเขาแน่นอน แต่มันมีประโยชน์จริง ๆ upvoted!
Dario Russo

2
มันจะหยุดเมื่อพารามิเตอร์ที่ว่างเปล่าถูกส่งผ่าน คุณควรตรวจสอบ$#
Sebi

args ดีทดสอบ, เห็นด้วยกับ"", ''เป็นอาร์กิวเมนต์ถ้าไม่มี args มันจะเงียบ ผมพยายามที่จะแก้ไขปัญหานี้ $#แต่จำเป็นสำหรับห่วงและเคาน์เตอร์กับ ฉันเพิ่งเพิ่มสิ่งนี้ในตอนท้าย:echo "End of args or received quoted null"
เมอร์ลิน

8

SUN Unix ของฉันมีข้อ จำกัด มากมายแม้ "$ @" ก็ไม่ได้ตีความตามที่ต้องการ วิธีแก้ปัญหาของฉันคือ $ {@} ตัวอย่างเช่น,

#!/bin/ksh
find ./ -type f | xargs grep "${@}"

อย่างไรก็ตามฉันต้องมีสคริปต์นี้เพราะ Unix ของฉันไม่รองรับ grep -r


นี่คือคำถามทุบตี; คุณกำลังใช้ksh
tripleee

6

หากคุณรวม$@ไว้ในสตริงที่ยกมาพร้อมกับตัวละครอื่น ๆ พฤติกรรมจะแปลกมากเมื่อมีหลายอาร์กิวเมนต์อาร์กิวเมนต์แรกเท่านั้นที่จะถูกรวมอยู่ในเครื่องหมายคำพูด

ตัวอย่าง:

#!/bin/bash
set -x
bash -c "true foo $@"

อัตราผลตอบแทน:

$ bash test.sh bar baz
+ bash -c 'true foo bar' baz

แต่การกำหนดให้กับตัวแปรอื่นก่อน:

#!/bin/bash
set -x
args="$@"
bash -c "true foo $args"

อัตราผลตอบแทน:

$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'

3
ฉันจะไม่ปฏิเสธสิ่งที่ทำให้สับสน แต่จริงๆแล้วมันสมเหตุสมผลในความหมายของการ"$@"ทุบตี นอกจากนี้ยังช่วยแสดงให้เห็นถึงความแตกต่างที่สำคัญระหว่าง$@และ$*และทำไมทั้งคู่จึงมีประโยชน์ จากส่วนbash(1)man พารามิเตอร์พิเศษ: " *- เมื่อการขยายเกิดขึ้นภายในเครื่องหมายคำพูดคู่มันจะขยายเป็นคำเดียวที่มีค่าของแต่ละพารามิเตอร์ […] นั่น"$*"คือเทียบเท่ากับ"$1c$2c..."โดยที่c[ $IFS]." และแน่นอนว่าการใช้$*แทนที่จะ$@เป็นตัวอย่างแรกของคุณจะได้ผลลัพธ์สุทธิเหมือนรุ่นที่สอง
FeRD

2
"$@"ตอนนี้เปรียบเทียบกับ อีกครั้งจาก man page: " @- เมื่อการขยายเกิดขึ้นภายในเครื่องหมายคำพูดคู่แต่ละพารามิเตอร์จะขยายเป็นคำแยกนั่น"$@"คือเท่ากับ"$1" "$2"…หากการขยายที่มีเครื่องหมายคำพูดคู่เกิดขึ้นภายในคำ ด้วยส่วนเริ่มต้นของคำดั้งเดิมและการขยายตัวของพารามิเตอร์สุดท้ายจะรวมกับส่วนสุดท้ายของคำเดิม " ... และแน่นอนถ้ารหัสของคุณได้รับbash -c "true foo $@ bar baz"แล้วใช้มันเป็นหากว่าสุทธิtest.sh one two bash -c 'true foo one' 'two bar baz'
FeRD

1
ขอบคุณสำหรับการอ้างอิงเอกสารและข้อมูลเกี่ยวกับ$*ฉันดูเหมือนจะลืมว่ามันมีอยู่ ..
ColinM

หึ ฉันเป็นคนตรงกันข้ามและ$@เพิ่งได้รับแรงฉุดเมื่อฉันเริ่มเชลล์สคริปต์ครั้งแรกฉันยังต้องเตือนตัวเองว่ามันอยู่ที่นั่น มันเป็นเรื่องธรรมดาที่จะเห็นการ"$*"ใช้สคริปต์ ... จากนั้นผู้เขียนจะรู้ว่ามันเป็นข้อโต้แย้งทั้งหมดของพวกเขาร่วมกันดังนั้นพวกเขาจึงลองใช้เรื่องไร้สาระที่ซับซ้อนด้วยการแยกคำ - "$*"หรือแยกประกอบรายการหาเรื่องด้วยการวนซ้ำ มากกว่าshiftที่จะดึงพวกเขาลงหนึ่งโดยหนึ่ง ... เพียงแค่ใช้$@แก้มัน (ช่วยให้ทุบตีที่ใช้ช่วยในการจำเหมือนกันในการเข้าถึงสมาชิกของอาร์เรย์เช่นกัน: ${var[*]}สำหรับพวกเขาทั้งหมดเป็นคำ${var[@]}สำหรับรายการของคำ)
FeRD

ปัญหาที่แท้จริงของที่นี่คือคุณกำลังใช้bash -cวิธีที่ไม่สมเหตุสมผล
tripleee

4

บางครั้งคุณต้องการที่จะผ่านการขัดแย้งของคุณทั้งหมด แต่นำหน้าด้วยธง (เช่น--flag)

$ bar --flag "$1" --flag "$2" --flag "$3"

คุณสามารถทำได้ด้วยวิธีต่อไปนี้:

$ bar $(printf -- ' --flag "%s"' "$@")

ทราบ:เพื่อหลีกเลี่ยงการแยกข้อมูลพิเศษคุณจะต้องพูด%sและ$@และเพื่อหลีกเลี่ยงการมีสายเดียวที่คุณจะไม่สามารถพูด subshell printfของ


3

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

สิ่งนี้อาจมีประโยชน์ แต่น่าเกลียดมาก

_command_opts=$( echo "$@" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&@",$i) ; gsub(/ $/,"",$i );gsub (/$/,"@",$i) }; print $0 }' | tr '@' \' )

3

คำตอบมากมายที่นี่แนะนำ$@หรือ$*ไม่ว่าจะมีและไม่มีคำพูด แต่ดูเหมือนไม่มีใครอธิบายสิ่งเหล่านี้ทำจริง ๆ และทำไมคุณควรทำเช่นนั้น ดังนั้นฉันจะขโมยข้อสรุปที่ยอดเยี่ยมนี้จากคำตอบนี้ :

ป้อนคำอธิบายรูปภาพที่นี่

ขอให้สังเกตว่าเครื่องหมายคำพูดสร้างความแตกต่างและไม่มีทั้งคู่มีพฤติกรรมเหมือนกัน

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

# file: parent.sh
# we have some params passed to parent.sh 
# which we will like to pass on to child.sh as-is

./child.sh $*

สังเกตคำพูดไม่ได้และ$@ควรจะทำงานได้ดีในสถานการณ์ข้างต้น


2

bar "$@" จะเทียบเท่า bar "$1" "$2" "$3" "$4"

ขอให้สังเกตว่าเครื่องหมายคำพูดมีความสำคัญ!

"$@", $@, "$*"หรือ$*แต่ละคนจะประพฤติแตกต่างกันเล็กน้อยเกี่ยวกับการหลบหนีและการเรียงต่อกันตามที่อธิบายในเรื่องนี้คำตอบ StackOverflow

กรณีการใช้งานที่เกี่ยวข้องอย่างใกล้ชิดคือการส่งผ่านอาร์กิวเมนต์ที่กำหนดทั้งหมดภายในอาร์กิวเมนต์เช่นนี้:

bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\"".

ฉันใช้รูปแบบของคำตอบ @ kvantour เพื่อบรรลุสิ่งนี้:

bash -c "bar $(printf -- '"%s" ' "$@")"

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