เชลล์สคริปต์สามารถพิมพ์อาร์กิวเมนต์โดยอ้างอิงตามที่คุณต้องการเขียนลงในพร้อมท์เชลล์หรือไม่


9

ในเชลล์สคริปต์ความเข้าใจของฉันคือการ"$@"ขยายไปยังอาร์กิวเมนต์ของสคริปต์โดยอ้างอิงตามที่ต้องการ เช่นนี้ส่งต่อข้อโต้แย้งสคริปต์เพื่อ gcc:

gcc -fPIC "$@"

<<<แม้ว่าการใช้ไวยากรณ์ bash pass-to-stdin นั้น"@$"ไม่ได้ผลอย่างที่ฉันคาดไว้

#!/bin/bash
cat <<< "$@"

เรียกสคริปต์ตามที่./test.sh foo "bar baz"ให้ไว้

foo bar baz

ฉันคาดหวัง

foo "bar baz"

มีวิธีเขียนเชลล์สคริปต์ที่พิมพ์อาร์กิวเมนต์เป็นคุณจะเขียนไว้ใน shell prompt หรือไม่ ตัวอย่างเช่น: คำใบ้เกี่ยวกับคำสั่งที่จะใช้ถัดไปรวมถึงอาร์กิวเมนต์ของสคริปต์ในคำใบ้

คำตอบ:


5

ทีนี้"$@"ขยายไปยังรายการพารามิเตอร์ตำแหน่งหนึ่งอาร์กิวเมนต์ต่อพารามิเตอร์ตำแหน่ง

เมื่อคุณทำ:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmdจะถูกเรียกด้วยทั้ง 3 ข้อโต้แย้ง: สตริงที่ว่างเปล่า, และfoo bar blah<newline>blahเชลล์จะเรียกการเรียกของexecve()ระบบด้วยสิ่งต่อไปนี้:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

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

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

หรือด้วยการzshขอคำพูดประเภทต่าง ๆ :

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

หรือด้วยzsh, bashหรือksh93(ที่นี่เพื่อbash, YMMV กับกระสุนอื่น ๆ ):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

คุณสามารถใช้ตัวเลือก xtrace ของเชลล์ที่ทำให้เชลล์พิมพ์สิ่งที่มันจะดำเนินการ:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

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


5
`"$@"` expands to the script arguments, quoting them as needed

ไม่นี่ไม่ใช่สิ่งที่เกิดขึ้น การเรียกโปรแกรมใช้รายการของอาร์กิวเมนต์แต่ละอาร์กิวเมนต์เป็นสตริง เมื่อคุณรันโปรแกรมเปลือก./test.sh foo "bar baz"นี้สร้างการโทรที่มีสามข้อโต้แย้ง: ./test.sh, และfoo bar baz(อาร์กิวเมนต์ zeroth เป็นชื่อโปรแกรมซึ่งช่วยให้โปรแกรมสามารถรู้ได้ภายใต้ชื่อที่เรียกว่า) การอ้างถึงเป็นคุณลักษณะของเชลล์ไม่ใช่คุณลักษณะของการเรียกโปรแกรม เชลล์สร้างรายการนี้เมื่อมีการโทร

"$@"คัดลอกรายการอาร์กิวเมนต์ที่ส่งผ่านไปยังสคริปต์หรือฟังก์ชันโดยตรงไปยังรายการอาร์กิวเมนต์ในการโทรที่ใช้ ไม่มีการอ้างอิงเนื่องจากไม่มีการแยกเชลล์ในรายการเหล่านั้น

ในcat <<< "$@"คุณกำลังใช้งาน"$@"ในบริบทที่ต้องการสตริงเดี่ยว <<<operator` ต้องสตริงไม่ได้รายชื่อของสตริง ในบริบทนี้ทุบตีใช้องค์ประกอบของรายการและรวมกับช่องว่างในระหว่าง

สำหรับการดีบักสคริปต์หากคุณเรียกใช้set -x( set +xเพื่อปิด) ที่เปิดใช้งานโหมดติดตามที่แต่ละคำสั่งถูกพิมพ์ก่อนที่จะดำเนินการ ใน bash การติดตามนั้นมีเครื่องหมายคำพูดซึ่งทำให้สามารถวางคำสั่งกลับเข้าไปในเชลล์shได้

'\''ถ้าคุณมีสตริงและคุณต้องการที่จะเปิดเป็นไวยากรณ์แหล่งที่มาของเชลล์ที่จะแยกวิเคราะห์กลับเข้ามาในสายเดิมที่คุณสามารถล้อมรอบด้วยราคาเดียวและแทนที่ทุกคำพูดเดียวภายในสตริงที่มี

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

ไวยากรณ์การแทนที่สตริงคือ ksh93 / bash / zsh / mksh เฉพาะ ใน SH ธรรมดาคุณจะต้องวนรอบสตริง

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo

2

"$@" ขยายไปยังอาร์กิวเมนต์ของสคริปต์โดยอ้างอิงตามต้องการ

เรียงกันของ สำหรับวัตถุประสงค์ในทางปฏิบัติที่ควรใกล้พอและคู่มืออ้างอิงก็กล่าวเช่นนั้น"$@" is equivalent to "$1" "$2" ...

ดังนั้นด้วยพารามิเตอร์ทั้งสองfooและbar bazสิ่งเหล่านี้จะเหมือนกัน:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(ยกเว้นว่าหากพารามิเตอร์มีอักขระพิเศษแทนที่จะเป็นแค่สตริงธรรมดาพวกเขาจะไม่ถูกขยายอีกหลังจากขยาย$@และ$1... )

แต่แม้ว่าเราจะพิจารณา$@แทนที่ด้วยพารามิเตอร์ในเครื่องหมายคำพูดราคาจะไม่อยู่ที่นั่นechoเพื่อดูเช่นเดียวกับที่gccไม่ได้รับราคา

<<<เป็นข้อยกเว้นเล็กน้อยสำหรับกฎ"$@"== "$1" "$2" ...มีการกล่าวถึงอย่างชัดเจนว่าThe result is supplied as a single string to the command on its standard inputหลังจากผ่านพารามิเตอร์และการขยายตัวแปรและการลบเครื่องหมายคำพูดระหว่างกลุ่มอื่น ๆ ดังนั้นตามปกติ<<< "foo"ให้fooเป็นอินพุตในทางเดียวกันsomecmd "foo"เท่านั้นให้fooเป็นอาร์กิวเมนต์

เรียกสคริปต์ว่า./test.sh foo "bar baz"[... ] ฉันคาดหวัง foo "bar baz"

"foo" "bar baz"หากคำพูดที่ยังคงอยู่ก็ยังจะต้องมีการ เชลล์หรือคำสั่งที่รันอยู่ไม่มีความคิดใด ๆ ว่า quoting คืออะไรเมื่อคำสั่งรัน หรือถ้ามีการพูดถึงการเรียกของระบบเพียงแค่รับรายการของสตริงที่สิ้นสุดด้วย null และเครื่องหมายคำพูดเป็นเพียงคุณสมบัติของภาษาเชลล์ ภาษาอื่น ๆ อาจมีการประชุมอื่น ๆ


0

ทางออกอื่นสำหรับทุบตี

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash ไม่รองรับการแทนที่ซ้อนกันดังนั้นต้องขอบคุณ/programming/12303974/assign-array-to-variable#12304017เพื่อแสดงวิธีกำหนดอาร์เรย์อีกครั้ง ดูman bash( https://linux.die.net/man/1/bash ) สำหรับรายละเอียดเกี่ยวกับการทดแทนการขยายตัวและรูปแบบอาร์เรย์ (ภายใต้การขยายพารามิเตอร์)

การวิเคราะห์

Bash ทำให้พารามิเตอร์บรรทัดคำสั่งเป็นอาร์เรย์ $@

q เก็บตัวอักษร

อัญประกาศคู่รอบการขยายพารามิเตอร์${ ... }รักษาพารามิเตอร์แต่ละรายการเป็นองค์ประกอบที่แตกต่างและล้อมรอบพวกเขา( )ช่วยให้คุณสามารถกำหนดพวกเขาเป็นอาร์เรย์ให้กับตัวแปร

/#/$qในการขยายตัวพารามิเตอร์ทดแทนจุดเริ่มต้นของรูปแบบ (เช่น regex ^) ด้วยถ่าน quoting

/%/$qในการขยายตัวพารามิเตอร์ทดแทนจุดสิ้นสุดของรูปแบบ (เช่น regex $) ด้วยถ่านข้อความ

ใช้กรณี: การสืบค้น MySQL สำหรับรายการที่อยู่อีเมลจากบรรทัดคำสั่ง

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

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END

เพียงแค่ทราบว่าการอ้างอิงด้วยเครื่องหมายคำพูดคู่นั้นไม่ได้มีประโยชน์มากในการป้องกันแบ็กสแลช, การ$ขยายและเครื่องหมายคำพูดคู่อื่น ๆ นั่นเป็นเหตุผลที่คำตอบอื่น ๆ ใช้เครื่องหมายคำพูดเดี่ยวในขณะที่มีความยาวเพื่อจัดการอัญประกาศเดี่ยวภายในสตริงหรือใช้คุณลักษณะของเชลล์เองในการสร้างสำเนาที่ยกมาของสตริง
ilkkachu

@ilkkachu ตั้งข้อสังเกตอย่างถูกต้องแล้ว! นั่นเป็นเหตุผลที่ (ในเหตุผลอื่น ๆ ) ที่ฉันตอบคำถามก่อนหน้านี้ทั้งหมด นอกจากนี้ทำไมฉันจึงเพิ่มกรณีการใช้งานนี้ หวังว่าความสมบูรณ์แบบไม่ใช่ศัตรูของความดี
Jeff
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.