วิธีการส่งผ่านอาร์กิวเมนต์สคริปต์ทุบตีไปยัง subshell


13

ฉันมีสคริปต์ตัวตัดคำที่ทำงานบางอย่างแล้วส่งพารามิเตอร์ดั้งเดิมไปยังเครื่องมืออื่น:

#!/bin/bash
# ...
other_tool -a -b "$@"

สิ่งนี้ทำงานได้ดีเว้นแต่ "เครื่องมืออื่น ๆ " จะทำงานใน subshell:

#!/bin/bash
# ...
bash -c "other_tool -a -b $@"

ถ้าฉันเรียกสคริปต์ wrapper ของฉันเช่นนี้:

wrapper.sh -x "blah blup"

ดังนั้นจะส่งเฉพาะอาร์กิวเมนต์แรก (-x) ไปที่ "other_tool" เท่านั้น ในความเป็นจริงฉันไม่ได้สร้าง subshell แต่ส่งผ่านอาร์กิวเมนต์ดั้งเดิมไปยัง shell บนโทรศัพท์ Android ซึ่งไม่ควรสร้างความแตกต่าง:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"

คำตอบ:


14

printfคำสั่งของ Bash มีคุณสมบัติที่จะอ้างอิง / escape / สตริงใดก็ได้ตราบใดที่ทั้ง parent และ subshell เป็น bash จริง ๆ มันควรจะได้ผล:

[แก้ไข: ตามที่ siegi ชี้ให้เห็นในความคิดเห็นหากคุณทำสิ่งนี้อย่างชัดเจนว่ามีปัญหาเมื่อไม่มีการโต้แย้งใด ๆ ซึ่งมันทำหน้าที่เหมือนมีอาร์กิวเมนต์ที่ว่างเปล่าเพียงครั้งเดียว ฉันได้เพิ่มการแก้ปัญหาด้านล่างห่อสตริงรูปแบบด้วย${1+}ซึ่งรวมถึงรูปแบบของสตริงถ้าอาร์กิวเมนต์แรกถูกกำหนด มันค่อนข้างเล็ก แต่ก็ใช้ได้ดี]

#!/bin/bash

quoted_args="$(printf "${1+ %q}" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

โปรดทราบว่าคุณสามารถทำได้ในบรรทัดเดียว: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"


ขอบคุณ! มันใช้งานได้ดีมากสำหรับฉัน
DribblzAroundU82

สิ่งนี้ทำงานได้ไม่ถูกต้องเมื่อไม่มีการส่งอาร์กิวเมนต์ (ผ่านอาร์กิวเมนต์ว่างเปล่าเดียวแทนที่จะไม่มีอาร์กิวเมนต์)
siegi

1
@siegi จับได้ดี; ฉันได้เพิ่มวิธีแก้ปัญหาสำหรับสิ่งนี้
Gordon Davisson

4

ไม่มีวิธีแก้ปัญหาใดทำงานได้ดี เพียงผ่าน x / \ \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" เป็นพารามิเตอร์และพวกเขาล้มเหลว

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

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}

1
ยังมีประโยชน์หากคุณพยายามต่อเชื่อม $ @ ซึ่งเป็นส่วนหนึ่งของอาร์กิวเมนต์สตริงที่ยกมาในที่สุดเช่น '/ bin / foo' "$ @" '--opt' และค้นพบว่ามันไม่ออกมาอย่างที่คุณคาดหวัง .
jrg

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

2

เปลี่ยนไป$@ $*ฉันทำแบบทดสอบท้องถิ่นเล็ก ๆ และทำงานในกรณีของฉัน

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

การบันทึกเป็นtest.shและทำให้สามารถเรียกทำงานได้

$ ./test.sh foo bar
foo bar
foo

มีความแตกต่างเล็กน้อยระหว่าง$*และ$@อย่างที่คุณเห็น ดูเช่นhttp://ss64.com/bash/syntax-parameters.html


สำหรับคำถามที่ตามมาในความคิดเห็น: คุณต้องหลบหนีเช่นพื้นที่สีขาว "สองครั้ง" เพื่อส่งผ่านสตริงที่มีตัวคั่นเป็นอาร์กิวเมนต์ที่รวมกันเช่นกับการtest.shปรับเปลี่ยนเป็นwcเสื้อคลุม:

#!/bin/sh
bash -c "wc $*"

งานนี้:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

แต่:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total

น่าเสียดายที่นี่ไม่ได้ผลเหมือนกัน หากคุณเรียกใช้ wrapper เช่นนี้wrapper.sh -x "blah blup"แล้ว subshell จะรับพารามิเตอร์สาม (-x, blah, blup) แทน TWO (-x, "blah blup")
Ralf Holly

คุณจะต้อง "คู่หนี argument" "Blah\ blup"ถ้าคุณต้องการแยกพื้นที่ที่จะถูกเก็บรักษาไว้คือ ลองใช้และดูว่าใช้ได้หรือไม่
Daniel Andersson

2
ดูเหมือนว่าจะใช้งานได้ แต่สิ่งนี้จะต้องมีผู้เรียก wrapper.sh เพื่อเพิ่มทางหนีและฉันกำลังมองหาวิธีแก้ปัญหาที่โปร่งใส
Ralf Holly

2

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

สิ่งนี้อาจทำให้คุณใกล้ชิดมากขึ้น:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

แน่นอนพารามิเตอร์ใด ๆ ที่มีคำพูดเดียวจะทำให้เกิดปัญหา


2

ตกลงอธิบายเพิ่มเติม:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third

1

ฉันพยายามแก้ปัญหาที่แตกต่างกันเป็นทรัพยากรที่ดีรวมทั้งข้อมูลพื้นฐานและทางเลือกสำหรับตัวอย่างBashFAQ / 096 บนของเกร็ก (aka. GreyCat) ที่วิกิพีเดีย ทั้งหมดฉันพบว่าทั้งสองต่อไปนี้จะอ่านได้มากที่สุด (จากคนที่ทำงาน):


ตั้งแต่ Bash 4.4 (เท่าที่ฉันสามารถบอกได้จากข่าว ) มันเป็นไปได้ที่จะใช้การขยายพารามิเตอร์ @Qเช่นนี้

adb sh -c "other_tool -a -b ${*@Q}"

โปรดทราบว่าฉันใช้$*ที่นี่แทน$@เพราะคุณต้องการที่"other_tool -a -b ${*@Q}"จะเป็นหนึ่งสายเดียวแทนหนึ่งสายต่อการโต้แย้ง

หากคุณต้องการทำเช่นเดียวกันกับตัวแปรอาร์เรย์ของ bashคุณจะต้องใช้ไวยากรณ์${ARRAY[*]@Q}(ภายในเครื่องหมายคำพูด)


หากคุณไม่มี Bash 4.4 หรือใหม่กว่าหรือไม่แน่ใจนี่เป็นโซลูชันที่ฉันต้องการ:

function escapeBashArgs() {
    local arg separator=""
    for arg
    do
        printf "%s%q" "$separator" "$arg"
        separator=" "
    done
}

adb sh -c "other_tool -a -b $(escapeBashArgs "$@")"

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

หากคุณไม่สนใจพื้นที่เพิ่มเติมด้านหน้าอาร์กิวเมนต์แรกคุณสามารถเปลี่ยนprintfสตริงรูปแบบเป็น" %q"และลบseparatorตัวแปรได้ หรือคุณสามารถใช้หนึ่งซับจากคำตอบของกอร์ดอน Davissons


โซลูชันนี้ใช้ได้กับทุกกรณีที่ฉันสามารถทำได้โดยเฉพาะ:

  • ไม่มีข้อโต้แย้ง: escapeBashArgsไม่มีอะไร
  • อาร์กิวเมนต์ว่างเปล่า: escapeBashArgs "" ""'' ''
  • อาร์กิวเมนต์ที่มีเพียงช่องว่าง: escapeBashArgs " " " "' ' ' 'หรือ\ \ \ \ \( การแสดงผลช่องว่างสุดท้ายจะถูกแสดงโดยไซต์นี้ )
  • อาร์กิวเมนต์ที่มีระยะห่างพิเศษและบรรทัดใหม่: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'หรือa\ \ \ \ \ \ b c\ d $'arg with\nnewline'( ขึ้นบรรทัดใหม่อยู่ระหว่างwithและnewlineในตำแหน่งอื่น ๆ เนื่องจากการตัดบรรทัดบนไซต์นี้ )
  • อาร์กิวเมนต์ที่มีอักขระพิเศษ: escapeBashArgs '$"'\''({:})''$"'\''({:})'หรือ\$\"\'\(\{:\}\)
  • ตัวอย่างจากคำตอบ jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'หรือx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(ทดสอบกับ GNU bash 5.0.3 (1) - ปล่อยใหม่)


-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"

3
ยินดีต้อนรับสู่ Super User! เรากำลังมองหาคำตอบยาว ๆ ที่ให้คำอธิบายและบริบท อย่าเพียงแค่ให้คำตอบเดียว อธิบายว่าทำไมคำตอบของคุณถึงถูกต้องนึกคิดด้วยการอ้างอิง คำตอบที่ไม่รวมคำอธิบายอาจถูกลบออก
G-Man กล่าวว่า 'Reinstate Monica'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.