parameter gaya dd ke skrip bash


19

Saya ingin meneruskan params ke skrip bash, dd-style. Pada dasarnya, saya mau

./script a=1 b=43

memiliki efek yang sama dengan

a=1 b=43 ./script

Saya pikir saya bisa mencapai ini dengan:

for arg in "$@"; do
   eval "$arg";
done

Apa cara yang baik untuk memastikan bahwa evalaman, yaitu yang "$arg"cocok dengan penugasan variabel statis (tanpa eksekusi kode)?

Atau adakah cara yang lebih baik untuk melakukan ini? (Saya ingin membuat ini tetap sederhana).


Ini ditandai dengan bash. Apakah Anda menginginkan solusi yang sesuai dengan Posix, atau apakah Anda akan menerima solusi bash?
rici

Apa kata tag itu yang saya maksud :)
PSkocik

Yah Anda bisa menguraikannya sebagai pola dengan =pemisah dan melakukan tugas dengan eval yang dibangun lebih hati-hati. Hanya untuk keamanan, untuk penggunaan pribadi, saya akan melakukannya seperti yang Anda lakukan.
orion

Jawaban:


16

Anda dapat melakukan ini di bash tanpa eval (dan tanpa pelarian buatan):

for arg in "$@"; do
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; then
    declare +i +a +A "$arg"
  fi
done

Sunting: Berdasarkan komentar oleh Stéphane Chazelas, saya menambahkan tanda pada deklarasi untuk menghindari agar variabel yang ditugaskan tidak dideklarasikan sebagai variabel array atau integer, yang akan menghindari sejumlah kasus yang declareakan mengevaluasi nilai bagian dari key=valargumen. (Itu+a Akan menyebabkan kesalahan jika variabel yang akan ditetapkan sudah dinyatakan sebagai variabel array, misalnya.) Semua kerentanan ini berkaitan dengan menggunakan sintaks ini untuk menetapkan kembali variabel yang ada (array atau integer), yang biasanya akan terkenal variabel shell.

Pada kenyataannya, ini hanya sebuah instance dari kelas serangan injeksi yang akan sama-sama mempengaruhi evalsolusi berbasis: itu akan jauh lebih baik untuk hanya mengizinkan nama argumen yang diketahui daripada secara membabi buta mengatur variabel mana saja yang kebetulan hadir dalam baris perintah. (Pertimbangkan apa yang terjadi jika baris perintah menetapkan PATH, misalnya. Atau mengatur ulangPS1 untuk menyertakan beberapa evaluasi yang akan terjadi pada tampilan prompt berikutnya.)

Daripada menggunakan variabel bash, saya lebih suka menggunakan array asosiatif dari argumen bernama, yang keduanya lebih mudah diatur, dan jauh lebih aman. Atau, itu bisa mengatur variabel bash aktual, tetapi hanya jika nama mereka dalam array asosiatif argumen yang sah.

Sebagai contoh dari pendekatan yang terakhir:

# Could use this array for default values, too.
declare -A options=([bs]= [if]= [of]=)
for arg in "$@"; do
  # Make sure that it is an assignment.
  # -v is not an option for many bash versions
  if [[ $arg =~ ^[[:alpha:]_][[:alnum:]_]*= &&
        ${options[${arg%%=*}]+ok} == ok ]]; then
    declare "$arg"
    # or, to put it into the options array
    # options[${arg%%=*}]=${arg#*=}
  fi
done

1
Regex tampaknya memiliki tanda kurung yang salah. Mungkin menggunakan ini sebagai gantinya: ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047: foo=adalah satu-satunya cara untuk mengatur foo ke string kosong, jadi harus diizinkan (IMHO). Saya memperbaiki kurung, terima kasih.
rici

3
declareadalah tentang sama berbahayanya eval(orang bahkan dapat mengatakan lebih buruk karena tidak jelas bahwa itu sama berbahayanya). Coba misalnya menyebutnya dengan 'DIRSTACK=($(echo rm -rf ~))'argumen.
Stéphane Chazelas

1
@PSkocik: +xis "not -x". -a= array yang diindeks, -A= array asosiatif, -i= variabel integer. Jadi: bukan array yang diindeks, bukan array asosiatif, bukan integer.
lcd047

1
Perhatikan bahwa dengan versi berikutnya bash, Anda mungkin perlu menambahkan +cuntuk menonaktifkan variabel gabungan atau +Funtuk menonaktifkan variabel mengambang. Saya masih akan menggunakan di evalmana Anda tahu di mana Anda berdiri.
Stéphane Chazelas

9

Yang POSIX (set $<prefix>varbukannya $varuntuk menghindari masalah dengan variabel khusus seperti IFS/ PATH...):

prefix=my_prefix_
for var do
  case $var in
    (*=*)
       case ${var%%=*} in
         "" | *[!abcdefghijiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*) ;;
         (*) eval "$prefix${var%%=*}="'${var#*=}'
       esac
  esac
done

Disebut sebagai myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2, itu akan menetapkan:

my_prefix_x <= 1
my_prefix_PATH <= /tmp/evil
my_prefix_1 <= 2

6

solusi lcd047 di refactored dengan DD_OPT_awalan hardcoded :

while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
  eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
done

frostschutz layak mendapatkan kredit untuk sebagian besar refactoring.

Saya menempatkan ini dalam file sumber dengan sebagai variabel global:

DD_OPTS_PARSE=$(cat <<'EOF'
  while [[ $1 =~ ^[[:alpha:]_][[:alnum:]_]*= ]]; do
    eval "DD_OPT_${1%%=*}"='${1#*=}'; shift;
  done
EOF
)

eval "$DD_OPTS_PARSE" melakukan semua keajaiban.

Versi untuk fungsi adalah:

DD_OPTS_PARSE_LOCAL="${PARSE_AND_REMOVE_DD_OPTS/DD_OPT_/local DD_OPT_}"

Digunakan:

eval "$DD_OPTS_PARSE_LOCAL"

Saya membuat repo dari ini, lengkap dengan tes dan README.md. Kemudian saya menggunakan ini dalam bungkus Github API CLI yang saya tulis, dan saya menggunakan bungkus yang sama untuk mengatur klon github dari kata repo (bootstrap itu menyenangkan).

Melewati parameter aman untuk skrip bash hanya dalam satu baris. Nikmati. :)


1
tetapi Anda dapat menyingkirkan *=*dan berhenti mengganti kunci / val di mana tidak ada =. (karena Anda refactoring): P
frostschutz

1
sebenarnya Anda bisa menghilangkan for for dan the if dan menggunakan $ 1 sebagai gantinya, karena Anda bergeser dan semuanya ...
frostschutz

1
Heh, bukti bahwa brainstorming berfungsi. :)
lcd047

1
Memanen ide-ide pagi: Anda bahkan dapat menyingkirkan keydan val, dan hanya menulis eval "${1%%=*}"=\${1#*=}. Tapi itu sudah sejauh ini, eval "$1"karena di @ rici declare "$arg"tidak akan bekerja, jelas. Waspadalah juga dalam mengatur hal-hal seperti PATHatau PS1.
lcd047

1
Terima kasih - saya pikir itu adalah variabel yang dievaluasi. Saya menghargai kesabaran Anda dengan saya - itu cukup mencolok. Lagi pula, tidak - selain yang dibayangkan, itu terlihat bagus. Anda tahu bahwa Anda dapat memperpanjang ini untuk bekerja dengan shell apa pun case. Mungkin itu tidak masalah, tapi kalau-kalau Anda tidak tahu ...
mikeserv

5

Shell Bourne klasik didukung, dan shell Bash dan Korn masih mendukung, sebuah -kopsi. Ketika itu berlaku, ddopsi perintah '-seperti' di mana saja pada baris perintah dikonversi secara otomatis menjadi variabel lingkungan yang diteruskan ke perintah:

$ set -k
$ echo a=1 b=2 c=3
$ 

Agak sulit untuk meyakinkan bahwa mereka adalah variabel lingkungan; menjalankan ini bekerja untuk saya:

$ set -k
$ env | grep '^[a-z]='   # No environment a, b, c
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: 
a=1
b=2
c=3
$ set +k
$ bash -c 'echo "Args: $*" >&2; env' a=1 b=2 c=3 | grep '^[a-z]='
Args: b=2 c=3
$

Yang pertama env | grepmenunjukkan tidak ada variabel lingkungan dengan satu huruf kecil. Yang pertama bashmenunjukkan bahwa tidak ada argumen yang diteruskan ke skrip dieksekusi melalui -c, dan lingkungan memang mengandung tiga variabel huruf tunggal. The set +kmembatalkan -k, dan menunjukkan bahwa perintah yang sama sekarang memiliki argumen berlalu untuk itu. (Itu a=1diperlakukan sebagai$0 naskah; Anda dapat membuktikannya juga, dengan gema yang sesuai.)

Ini mencapai apa yang ditanyakan oleh pertanyaan - bahwa mengetik ./script.sh a=1 b=2harus sama dengan mengetik a=1 b=2 ./script.sh.

Perlu diketahui bahwa Anda mengalami masalah jika Anda mencoba trik seperti ini di dalam skrip:

if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

Ini "$@"diperlakukan kata demi kata; itu tidak dianalisis kembali untuk menemukan variabel gaya penugasan (dalam keduanya bashdan ksh). Saya mencoba:

#!/bin/bash

echo "BEFORE"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='
if [ -z "$already_invoked_with_minus_k" ]
then set -k; exec "$0" "$@" already_invoked_with_minus_k=1
fi

echo "AFTER"
echo "Arguments:"
al "$@"
echo "Environment:"
env | grep -E '^([a-z]|already_invoked_with_minus_k)='

unset already_invoked_with_minus_k

dan hanya already_invoked_with_minus_kvariabel lingkungan diatur dalam execskrip d.


Jawaban yang sangat bagus! Sangat menarik bahwa ini tidak akan mengubah PATH, meskipun HOME dapat diubah sehingga harus ada sesuatu seperti daftar hitam (berisi PATH setidaknya) dari env vars yang akan terlalu berbahaya untuk diatur dengan cara ini. Saya suka bagaimana ini sangat singkat dan menjawab pertanyaan, tapi saya akan pergi dengan solusi awalan + eval + sanitasi karena masih lebih aman dan dengan demikian dapat digunakan secara universal (dalam lingkungan di mana Anda tidak ingin pengguna mengacaukan lingkungan ). Terima kasih dan +1.
PSkocik

2

Usaha saya:

#! /usr/bin/env bash
name='^[a-zA-Z][a-zA-Z0-9_]*$'
count=0
for arg in "$@"; do
    case "$arg" in
        *=*)
            key=${arg%%=*}
            val=${arg#*=}

            [[ "$key" =~ $name ]] && { let count++; eval "$key"=\$val; } || break

            # show time
            if [[ "$key" =~ $name ]]; then
                eval "out=\${$key}"
                printf '|%s| <-- |%s|\n' "$key" "$out"
            fi
            ;;
        *)
            break
            ;;
    esac
done
shift $count

# show time again   
printf 'arg: |%s|\n' "$@"

Ia bekerja dengan (hampir) sampah sembarang pada RHS:

$ ./assign.sh Foo_Bar33='1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0' '1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33'
|Foo_Bar33| <-- |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0|
arg: |1 2;3`4"5~6!7@8#9$0 1%2^3&4*5(6)7-8=9+0=33|

$ ./assign.sh a=1 b=2 c d=4
|a| <-- |1|
|b| <-- |2|
arg: |c|
arg: |d=4|

shift akan membunuh hal-hal yang salah jika Anda tidak memutus perulangan pada parameter non-x = y pertama
frostschutz

@frostschutz Poin bagus, diedit.
lcd047

Pekerjaan yang bagus untuk menggeneralisasikannya. Saya pikir itu bisa disederhanakan sedikit.
PSkocik

Apakah Anda mendapatkan kesempatan untuk melihat hasil edit saya?
PSkocik

Silakan lihat edit saya. Itulah cara saya menyukainya (+ mungkin shiftmalah bukan shift 1). Jika tidak terima kasih!
PSkocik

0

Beberapa waktu yang lalu saya memilih aliasuntuk pekerjaan semacam ini. Inilah beberapa jawaban saya yang lain:


Kadang-kadang dimungkinkan untuk memisahkan evaluasi dan pelaksanaan pernyataan tersebut. Misalnya, aliasdapat digunakan untuk mengevaluasi dulu suatu perintah. Dalam contoh berikut ini, definisi variabel disimpan ke alias yang hanya dapat dinyatakan dengan sukses jika $varvariabel yang dievaluasi tidak mengandung byte yang tidak cocok dengan alfanumerik ASCII atau _.

LC_OLD=$LC_ALL LC_ALL=C
for var do    val=${var#*=} var=${var%%=*}
    alias  "${var##*[!_A-Z0-9a-z]*}=_$var=\$val" &&
    eval   "${var##[0-9]*}" && unalias "$var"
done;       LC_ALL=$LC_OLD

evaldigunakan di sini untuk menangani memohon yang baru aliasdari konteks varname yang dikutip - bukan untuk tugas yang tepat. Dan evalhanya dipanggil sama sekali jika aliasdefinisi sebelumnya berhasil, dan sementara saya tahu banyak implementasi yang berbeda akan menerima banyak jenis nilai untuk nama alias, saya belum menemukan shell yang akan menerima yang benar-benar kosong .

Namun, definisi dalam alias adalah untuk _$var, dan ini untuk memastikan bahwa tidak ada nilai lingkungan signifikan yang dituliskan. Saya tidak tahu nilai lingkungan penting apa pun yang dimulai dengan _ dan biasanya ini merupakan taruhan yang aman untuk deklarasi semi-pribadi.

Lagi pula, jika definisi alias berhasil, itu akan mendeklarasikan alias bernama untuk $varnilai. Dan evalhanya akan memanggil itu aliasjika juga tidak dimulai dengan angka - yang lain evalhanya mendapatkan argumen nol. Jadi jika kedua kondisi terpenuhi evalpanggilan aliasdan definisi variabel disimpan dalam aliasdibuat, setelah itu alias baru segera dihapus dari tabel hash.


Yang juga bermanfaat aliasdalam konteks ini adalah Anda dapat mencetak karya Anda. aliasakan mencetak pernyataan aman-untuk-eksekusi kembali shell yang dikutip dua kali lipat ketika ditanya.

sh -c "IFS=\'
    alias q=\"\$*\" q" -- \
    some args which alias \
    will print back at us

KELUARAN

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
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.