tham số kiểu dd cho tập lệnh bash


19

Tôi muốn chuyển params cho một tập lệnh bash, kiểu dd. Về cơ bản, tôi muốn

./script a=1 b=43

có tác dụng tương tự như

a=1 b=43 ./script

Tôi nghĩ rằng tôi có thể đạt được điều này với:

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

Cách tốt nào để đảm bảo rằng evalan toàn, tức là "$arg"phù hợp với tĩnh (không thực thi mã), gán biến?

Hoặc là có một cách tốt hơn để làm điều này? (Tôi muốn giữ đơn giản này).


Điều này được gắn thẻ với bash. Bạn có muốn một giải pháp tuân thủ Posix, hoặc bạn sẽ chấp nhận các giải pháp bash?
ngày

Những gì thẻ nói là những gì tôi muốn nói :)
PSkocik

Vâng, bạn chỉ có thể phân tích nó như một mô hình với một =dấu phân cách và thực hiện nhiệm vụ với một eval được xây dựng cẩn thận hơn. Chỉ vì sự an toàn, cho mục đích cá nhân, tôi sẽ làm như bạn đã làm.
orion

Câu trả lời:


16

Bạn có thể làm điều này trong bash mà không có eval (và không có lối thoát nhân tạo):

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

Chỉnh sửa: Dựa trên nhận xét của Stéphane Chazelas, tôi đã thêm cờ vào khai báo để tránh biến được gán đã được khai báo là biến mảng hoặc biến nguyên, điều này sẽ tránh một số trường hợp declaresẽ đánh giá phần giá trị của key=valđối số. (Ví dụ, +asẽ gây ra lỗi nếu biến được đặt đã được khai báo là biến mảng). Tất cả các lỗ hổng này liên quan đến việc sử dụng cú pháp này để gán lại các biến (mảng hoặc số nguyên) hiện có, thường được biết đến biến vỏ.

Trên thực tế, đây chỉ là một ví dụ của một lớp các cuộc tấn công tiêm chích sẽ ảnh hưởng như nhau đến evalcác giải pháp dựa trên cơ sở: sẽ thực sự tốt hơn nếu chỉ cho phép các tên đối số đã biết hơn là đặt một cách mù quáng bất kỳ biến nào xuất hiện trong dòng lệnh. (Xem xét những gì xảy ra nếu dòng lệnh đặt PATH, ví dụ. Hoặc đặt lại PS1để bao gồm một số đánh giá sẽ xảy ra ở màn hình nhắc tiếp theo.)

Thay vì sử dụng các biến bash, tôi thích sử dụng một mảng kết hợp của các đối số được đặt tên, vừa dễ đặt, vừa an toàn hơn nhiều. Ngoài ra, nó có thể đặt các biến bash thực tế, nhưng chỉ khi tên của chúng nằm trong một mảng kết hợp của các đối số hợp pháp.

Như một ví dụ về cách tiếp cận sau:

# 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 dường như có dấu ngoặc sai. Có lẽ sử dụng điều này thay thế : ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047: foo=là cách duy nhất để đặt foo thành chuỗi trống, vì vậy nó nên được cho phép (IMHO). Tôi đã sửa dấu ngoặc, cảm ơn.
ngày

3
declarelà nguy hiểm như eval(người ta thậm chí có thể nói tồi tệ hơn vì nó không rõ ràng là nó nguy hiểm như vậy). Ví dụ thử gọi nó với 'DIRSTACK=($(echo rm -rf ~))'tư cách là đối số.
Stéphane Chazelas

1
@PSkocik: +xlà "không -x". -a= mảng được lập chỉ mục, -A= mảng kết hợp, -i= biến số nguyên. Do đó: không được lập chỉ mục mảng, không liên kết mảng, không số nguyên.
lcd047

1
Lưu ý rằng với phiên bản tiếp theo bash, bạn có thể cần thêm +cđể tắt các biến tổng hợp hoặc tắt các biến +Fnổi. Tôi vẫn sẽ sử dụng evalnơi bạn biết nơi bạn đứng.
Stéphane Chazelas

9

Một POSIX (đặt $<prefix>varthay vì $varđể tránh các vấn đề với các biến đặc biệt như IFS/ PATH...):

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

Được gọi là myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2, nó sẽ chỉ định:

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

6

Giải pháp của LCD047 được tái cấu trúc với DD_OPT_tiền tố mã hóa cứng :

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

frostschutz xứng đáng tín dụng cho hầu hết các tái cấu trúc.

Tôi đặt cái này trong một tệp nguồn với biến toàn cục:

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

eval "$DD_OPTS_PARSE" làm tất cả các phép thuật.

Một phiên bản cho các chức năng sẽ là:

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

Đang sử dụng:

eval "$DD_OPTS_PARSE_LOCAL"

Tôi đã thực hiện một repo từ điều này, hoàn thành với các bài kiểm tra và README.md. Sau đó, tôi đã sử dụng cái này trong trình bao bọc API Github mà tôi đang viết và tôi đã sử dụng cùng một trình bao bọc để thiết lập một bản sao github của repo đã nói (bootstrapping rất thú vị).

Thông số an toàn truyền cho các tập lệnh bash chỉ trong một dòng. Thưởng thức. :)


1
nhưng bạn có thể thoát khỏi *=*và dừng thay thế khóa / val khi không có =. (kể từ khi bạn tái cấu trúc): P
frostschutz

1
trong thực tế, bạn có thể thoát khỏi vòng lặp for và thay vào đó và sử dụng trong khi $ 1, vì bạn đang thay đổi và tất cả ...
frostschutz

1
Heh, bằng chứng cho thấy động não hoạt động. :)
lcd047

1
Thu hoạch ý tưởng buổi sáng: bạn thậm chí có thể loại bỏ keyval, và chỉ cần viết eval "${1%%=*}"=\${1#*=}. Nhưng điều đó gần như đi xa, eval "$1"như trong @ rici declare "$arg"sẽ không hoạt động, rõ ràng. Cũng hãy cẩn thận với việc thiết lập những thứ như PATHhoặc PS1.
lcd047

1
Cảm ơn bạn - Tôi nghĩ đó là biến được đánh giá. Tôi đánh giá cao sự kiên nhẫn của bạn với tôi - điều đó khá rõ ràng. Dù sao, không - khác với tưởng tượng đó, nó có vẻ tốt. Bạn biết mặc dù bạn có thể mở rộng nó để làm việc trong bất kỳ shell nào case. Có lẽ điều đó không thành vấn đề, nhưng chỉ trong trường hợp bạn không biết ...
mikeerv

5

Vỏ Bourne cổ điển được hỗ trợ, và vỏ Bash và Korn vẫn hỗ trợ, một -ktùy chọn. Khi nó có hiệu lực, mọi ddtùy chọn lệnh 'giống như' ở bất kỳ đâu trên dòng lệnh đều được tự động chuyển thành các biến môi trường được truyền cho lệnh:

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

Khó hơn một chút để thuyết phục rằng chúng là các biến môi trường; chạy cái này hoạt động với tôi:

$ 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
$

Đầu tiên env | grepchứng minh không có biến môi trường với một chữ cái viết thường. Điều đầu tiên bashcho thấy rằng không có đối số nào được truyền cho tập lệnh được thực thi thông qua -cvà môi trường có chứa ba biến đơn. Việc set +khủy bỏ -kvà cho thấy rằng cùng một lệnh hiện có các đối số được truyền cho nó. (Điều a=1này đã được xử lý như $0đối với kịch bản; bạn cũng có thể chứng minh điều đó với tiếng vang phù hợp.)

Điều này đạt được những gì câu hỏi yêu cầu - rằng gõ ./script.sh a=1 b=2nên giống như gõ a=1 b=2 ./script.sh.

Xin lưu ý rằng bạn gặp sự cố nếu bạn thử các thủ thuật như thế này trong tập lệnh:

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

Điều "$@"được xử lý nguyên văn; nó không được phân tích lại để tìm các biến kiểu gán (trong cả hai bashksh). Tôi đã thử:

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

và chỉ already_invoked_with_minus_kbiến môi trường được đặt trong exectập lệnh 'd.


Câu trả lời rất hay! Thật thú vị khi điều này sẽ không thay đổi PATH, mặc dù HOME có thể thay đổi, do đó phải có một cái gì đó giống như một danh sách đen (ít nhất là chứa PATH) của các lọ env sẽ quá nguy hiểm khi đặt theo cách này. Tôi thích cách này cực ngắn và trả lời câu hỏi, nhưng tôi sẽ sử dụng giải pháp tiền tố vệ sinh + eval + vì nó vẫn an toàn hơn và do đó có thể sử dụng phổ biến hơn (trong môi trường mà bạn không muốn người dùng gây rối với môi trường ). Cảm ơn và +1.
PSkocik

2

Nỗ lực của tôi:

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

Nó hoạt động với (hầu hết) rác tùy ý trên 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|

sự thay đổi sẽ giết chết những điều sai nếu bạn không phá vỡ vòng lặp ở tham số không phải x = y đầu tiên
frostschutz

@frostschutz Điểm tốt, đã chỉnh sửa.
lcd047

Công việc tốt đẹp trong việc khái quát nó. Tôi nghĩ rằng nó có thể được đơn giản hóa một chút.
PSkocik

Bạn đã có cơ hội để xem chỉnh sửa của tôi?
PSkocik

Hãy xem chỉnh sửa của tôi. Đó là cách tôi thích nó (+ có thể chỉ shiftthay vì shift 1). Nếu không thì cảm ơn!
PSkocik

0

Một thời gian trước tôi đã giải quyết aliascho loại công việc này. Đây là một số câu trả lời khác của tôi:


Tuy nhiên, đôi khi có thể tách rời việc đánh giá và thực hiện các tuyên bố đó. Ví dụ, aliascó thể được sử dụng để đánh giá trước một lệnh. Trong ví dụ sau, định nghĩa biến được lưu vào bí danh chỉ có thể được khai báo thành công nếu $varbiến mà nó đang đánh giá không chứa byte nào không khớp với chữ số ASCII hoặc _.

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

evalđược sử dụng ở đây để xử lý việc gọi mới aliastừ ngữ cảnh varname được trích dẫn - không phải cho phép gán chính xác. Và evalchỉ được gọi hoàn toàn nếu aliasđịnh nghĩa trước đó thành công và trong khi tôi biết rất nhiều cách triển khai khác nhau sẽ chấp nhận rất nhiều loại giá trị khác nhau cho tên bí danh, tôi vẫn chưa tìm thấy một vỏ sẽ chấp nhận một vỏ hoàn toàn trống .

_$varTuy nhiên, định nghĩa trong bí danh là dành cho và điều này là để đảm bảo rằng không có giá trị môi trường quan trọng nào được ghi lại. Tôi không biết về bất kỳ giá trị môi trường đáng chú ý nào bắt đầu bằng dấu _ và đó thường là đặt cược an toàn cho khai báo bán riêng.

Dù sao, nếu định nghĩa bí danh thành công, nó sẽ khai báo một bí danh được đặt tên cho $vargiá trị của. Và evalsẽ chỉ gọi rằng aliasnếu nó cũng không bắt đầu bằng một số - người khác evalchỉ nhận được một đối số null. Vì vậy, nếu cả hai điều kiện được đáp ứng đều evalgọi aliasvà định nghĩa biến được lưu trong đó aliasđược thực hiện, sau đó bí danh mới sẽ nhanh chóng bị xóa khỏi bảng băm.


Cũng hữu ích aliastrong bối cảnh này là bạn có thể in công việc của bạn. aliassẽ in một tuyên bố gấp đôi an toàn cho trích dẫn vỏ khi được yêu cầu.

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

ĐẦU RA

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.