Cara meneruskan argumen skrip bash ke sebuah subkulit


13

Saya memiliki skrip wrapper yang berfungsi dan kemudian meneruskan parameter asli ke alat lain:

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

Ini berfungsi dengan baik, kecuali "alat lain" dijalankan dalam subkulit:

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

Jika saya memanggil skrip pembungkus saya seperti ini:

wrapper.sh -x "blah blup"

kemudian, hanya argumen asli pertama (-x) yang diserahkan ke "other_tool". Pada kenyataannya, saya tidak membuat subkulit, tetapi meneruskan argumen asli ke shell di ponsel Android, yang seharusnya tidak membuat perbedaan:

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

Jawaban:


14

printfPerintah Bash memiliki fitur yang akan mengutip / melarikan diri / apa pun string, jadi selama kedua induk dan subkulit sebenarnya adalah bash, ini harus bekerja:

[Sunting: seperti yang siegi tunjukkan dalam komentar, jika Anda melakukan ini dengan cara yang jelas ada masalah ketika tidak ada argumen yang diberikan, di mana ia bertindak seperti sebenarnya ada satu argumen kosong. Saya telah menambahkan solusi di bawah ini, dengan membungkus string format ${1+}, yang hanya mencakup string format jika argumen pertama didefinisikan . Agak kluge, tapi itu berhasil.]

#!/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"

Perhatikan bahwa Anda juga dapat melakukannya dalam satu baris: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"


Terima kasih! Ini bekerja sangat baik untuk saya.
DribblzAroundU82

Ini tidak berfungsi dengan benar, ketika tidak ada argumen yang dilewatkan (itu melewati satu argumen kosong alih-alih tidak ada argumen).
siegi

1
@siegi Tangkapan bagus; Saya telah menambahkan solusi untuk ini.
Gordon Davisson

4

Tidak ada solusi yang berfungsi dengan baik. Lewati saja x / \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" sebagai parameter dan gagal.

Ini adalah pembungkus sederhana yang menangani setiap kasing. Catat bagaimana ia lolos dari setiap argumen dua kali.

#!/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
juga berguna jika Anda mencoba untuk menggabungkan $ @ sebagai bagian dari argumen string yang dikutip akhirnya seperti '/ bin / foo' "$ @" '--opt' dan menemukan bahwa itu tidak keluar seperti yang Anda harapkan .
jrg

1
Oh terima kasih Tuhan. Telah berjuang dengan masalah ini dan sepertinya tidak ada yang berhasil. Ini menyelesaikannya. Akan lebih baik jika itu entah bagaimana bash built-in untuk memohon lolos ganda mengingat masalah ini banyak muncul.
MooGoo

2

Ubah $@ke $*. Saya melakukan tes lokal kecil dan berfungsi dalam kasus saya.

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

Menyimpan sebagai test.shdan membuatnya dapat dijalankan memberi

$ ./test.sh foo bar
foo bar
foo

Ada perbedaan halus antara $*dan $@, seperti yang Anda lihat. Lihat misalnya http://ss64.com/bash/syntax-parameters.html


Untuk pertanyaan tindak lanjut dalam komentar: Anda perlu melarikan diri mis. Spasi putih "dua kali" untuk meneruskan string dengan pemisah sebagai argumen gabungan, misalnya dengan test.shdimodifikasi menjadi wcpembungkus:

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

Ini bekerja:

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

tapi:

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

Sayangnya, ini juga tidak berhasil. Jika Anda memanggil pembungkus seperti ini: wrapper.sh -x "blah blup"maka subkulit tersebut mendapatkan TIGA parameter (-x, bla, blup) alih-alih DUA (-x, "bla blup")
Ralf Holly

Anda perlu "meloloskan diri" argumen jika Anda ingin pemisah ruang dipertahankan, yaitu "Blah\ blup". Cobalah dan lihat apakah itu berhasil.
Daniel Andersson

2
Sepertinya ini berfungsi, bagaimanapun, ini akan membutuhkan penelepon wrapper.sh untuk menambahkan lolos, dan saya sedang mencari solusi transparan.
Ralf Holly

2

Gagal karena Anda memaksa array (parameter posisi) menjadi string. "$@"ajaib karena memberi Anda setiap parameter terpisah sebagai string yang dikutip dengan benar. Menambahkan teks tambahan mematahkan keajaiban: "blah $@"hanya sebuah string tunggal.

Ini bisa membuat Anda lebih dekat:

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

Tentu saja, parameter apa pun yang berisi kutipan tunggal akan menyebabkan masalah.


2

Ok, penjelasan lebih lanjut:

$ 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

Saya mencoba berbagai solusi, sumber yang bagus termasuk informasi latar belakang dan alternatifnya adalah misalnya BashFAQ / 096 di Greg's (alias. GreyCat's) Wiki . Secara keseluruhan saya menemukan dua berikut ini yang paling mudah dibaca (dari yang berfungsi):


Karena Bash 4.4 (sejauh yang saya tahu dari BERITA ) adalah mungkin untuk menggunakan ekspansi parameter @Q seperti ini:

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

Perhatikan bahwa saya menggunakan di $*sini bukan $@karena Anda ingin "other_tool -a -b ${*@Q}"menjadi satu string tunggal, bukan satu string per argumen yang diteruskan.

Jika Anda ingin melakukan hal yang sama dengan variabel array bash Anda akan memerlukan sintaks ${ARRAY[*]@Q}(dalam tanda kutip).


Jika Anda tidak memiliki Bash 4.4 atau yang lebih baru atau tidak yakin, ini adalah solusi pilihan saya:

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

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

Perhatikan bahwa di sini Anda perlu menggunakan "$@"alih-alih $@atau "$*"atau $*karena Anda tidak ingin pemisahan kata dalam argumen, sehingga varian tanpa kutip tidak dapat digunakan, dan Anda ingin jumlah argumen dipertahankan, jadi "$*"tidak dapat digunakan karena akan bergabung semua argumen menjadi string tunggal. Fungsi kemudian mengembalikan semua argumen dalam satu string.

Jika Anda tidak peduli dengan ruang tambahan di depan argumen pertama, Anda dapat mengubah printfstring format ke " %q"dan menghapus separatorvariabel. Atau Anda dapat menggunakan one-liner dari jawaban Gordon Davissons .


Solusi ini bekerja dengan semua kasus yang bisa saya buat, terutama:

  • Tidak ada argumen: escapeBashArgstidak ada
  • Argumen kosong: escapeBashArgs "" ""'' ''
  • Argumen dengan hanya spasi: escapeBashArgs " " " "' ' ' 'atau \ \ \ \ \( ruang terakhir dimakan oleh renderer situs ini )
  • Argumen dengan spasi khusus dan baris baru: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'atau a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( baris baru antara withdan newline, pada posisi lain karena pembungkus baris di situs ini )
  • Argumen dengan karakter khusus: escapeBashArgs '$"'\''({:})''$"'\''({:})'atau\$\"\'\(\{:\}\)
  • Contoh dari jawaban jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'ataux/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Diuji dengan GNU bash 5.0.3 (1) -release.)


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

3
Selamat Datang di Pengguna Super! Kami mencari jawaban panjang yang memberikan penjelasan dan konteks. Jangan hanya memberikan jawaban satu baris; jelaskan mengapa jawaban Anda benar, idealnya dengan kutipan. Jawaban yang tidak termasuk penjelasan dapat dihapus.
G-Man Mengatakan 'Reinstate Monica'
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.