bash 스타일의 dd 스타일 매개 변수


19

dd 스타일의 bash 스크립트에 매개 변수를 전달하고 싶습니다. 기본적으로 나는 원한다

./script a=1 b=43

같은 효과를

a=1 b=43 ./script

나는 이것을 가지고 이것을 달성 할 수 있다고 생각했다.

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

정적 (코드 실행 없음) 변수 할당과 일치하는 eval안전한지 확인하는 좋은 방법은 무엇입니까 "$arg"?

아니면 더 좋은 방법이 있습니까? (나는 이것을 단순하게 유지하고 싶다).


bash로 태그되었습니다. Posix 호환 솔루션을 원하십니까? 또는 bash 솔루션을 수락 하시겠습니까?
rici

태그의 말은 내 말은 :)
PSkocik

글쎄, 당신은 그것을 =구분 기호 로 패턴으로 구문 분석 하고보다 신중하게 구성된 평가로 할당을 수행 할 수 있습니다. 안전을 위해서, 개인 용도로, 나는 당신이 한 것처럼 그것을 할 것입니다.
오리온

답변:


16

eval없이 (그리고 인공적인 탈출없이) bash에서 이것을 할 수 있습니다 :

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

편집 : Stéphane Chazelas의 의견을 기반으로, 할당 된 변수가 이미 배열 또는 정수 변수로 선언되지 않도록 선언에 플래그를 추가 declare하여 key=val인수 의 값 부분을 평가 하는 많은 경우를 피할 수 있습니다. ( +a예를 들어 변수를 설정할 변수가 이미 배열 변수로 선언 된 경우 오류가 발생합니다.) 이러한 모든 취약점은이 구문을 사용하여 기존 (배열 또는 정수) 변수를 다시 할당하는 것과 관련이 있습니다. 일반적으로 잘 알려진 변수 쉘 변수.

실제로 이것은 eval기반 솔루션에 동일하게 영향을주는 일련 의 인젝션 공격 인스턴스 일뿐입니다. 명령 줄에 존재하는 변수를 맹목적으로 설정하는 것보다 알려진 인수 이름 만 허용하는 것이 훨씬 좋습니다. ( PATH예를 들어, 명령 행이을 설정하면 어떻게되는지 고려하십시오 . 또는 PS1다음 프롬프트 표시에서 발생할 평가를 포함하도록 재설정 합니다.)

bash 변수를 사용하는 대신 명명 된 인수의 연관 배열을 사용하는 것이 좋습니다.이 배열은 설정하기 쉽고 훨씬 안전합니다. 또는 실제 bash 변수를 설정할 수 있지만 해당 이름이 합법적 인 인수의 연관 배열에있는 경우에만 가능합니다.

후자의 접근 방식의 예로서 :

# 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
정규식에 괄호가 잘못 된 것 같습니다. 아마도 이것을 대신 사용하십시오 : ^[[:alpha:]_][[:alnum:]_]*=?
lcd047

1
@ lcd047 : foo=foo를 빈 문자열로 설정하는 유일한 방법이므로 허용되어야합니다 (IMHO). 나는 괄호를 고쳤다.
rici

3
declareeval( 위험한 것만 큼 명백하지 않은 것처럼 더 나쁜 말을 할 수도있다) 예를 들어 'DIRSTACK=($(echo rm -rf ~))'인수로 호출하십시오.
Stéphane Chazelas 2016 년

1
@PSkocik : +x는 "아님 -x"입니다. -a= 인덱스 배열, -A= 연관 배열, -i= 정수 변수. 따라서 : 정수 배열이 아닌 연관 배열이 아닌 인덱스 배열이 아닙니다.
lcd047

1
의 다음 버전에서는 복합 변수 를 비활성화하거나 부동 변수를 비활성화 bash하기 위해 추가해야 할 수도 있습니다. 나는 아직도 당신이 어디에 서 있는지 알고 사용 합니다. +c+Feval
Stéphane Chazelas 2016 년

9

POSIX 하나 ( / ... 와 같은 특수 변수의 문제를 피하기 위해 $<prefix>var대신 설정 ) :$varIFSPATH

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

로 호출 myscript x=1 PATH=/tmp/evil %=3 blah '=foo' 1=2하면 다음이 할당됩니다.

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

6

lcd047의 솔루션은 하드 코딩 된 DD_OPT_접두어로 리팩토링되었습니다 .

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

frostschutz 는 대부분의 리팩토링에 대한 크레딧을받을 자격이 있습니다.

이것을 전역 변수로 소스 파일에 넣었습니다.

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

eval "$DD_OPTS_PARSE" 모든 마술을한다.

함수의 버전은 다음과 같습니다.

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

사용:

eval "$DD_OPTS_PARSE_LOCAL"

나는 이것으로 repo를 만들었고 테스트와 README.md로 완성되었습니다. 그럼 내가 작성했다 랩퍼 Github에서 API CLI에서 이것을 사용하고, 나는 설정에 말했다의 GitHub의 클론 같은 래퍼를 사용 REPO을 (부트 스트랩 재미).

한 줄로 bash 스크립트에 대한 안전한 매개 변수 전달. 즐겨. :)


1
그러나 *=*=가없는 키 / val 대체를 제거 하고 중지 할 수 있습니다 . (당신은 리팩토링 된 이후) : P
frostschutz

1
사실 당신은 for 루프를 제거하고 당신이 이동하고있어 이후 대신에 $ 1 동안 경우에 사용하는 모든 ... 얻을 수 있습니다
frostschutz

1
허, 브레인 스토밍이 작동한다는 증거. :)
lcd047 2016 년

1
아침 아이디어 수집 : key및을 제거 val하고 바로 쓸 수도 eval "${1%%=*}"=\${1#*=}있습니다. 그러나 eval "$1"@rici의 declare "$arg"작동하지 않는 것처럼 분명히 그것이 진행되는 한 많이 있습니다. PATH또는 과 같은 것을 설정하는 것도주의하십시오 PS1.
lcd047

1
감사합니다-평가 변수라고 생각했습니다. 나는 당신과 함께 인내심을 주셔서 감사합니다-그것은 매우 눈에 띄게 분명하다. 어쨌든, 상상 한 것 외에는 좋아 보입니다. 을 사용하여 모든 쉘에서 작동하도록 확장 할 수는 있지만 알고 있습니다 case. 아마 그것은 중요하지 않지만, 당신이 모르는 경우에 대비해 ...
mikeserv

5

클래식 Bourne 셸이 지원되며 Bash 및 Korn 셸은 여전히 -k옵션입니다. 이것이 효과가있을 때 dd, 명령 행 어디에서나 ' -like'명령 옵션은 자동으로 명령에 전달 된 환경 변수로 변환됩니다.

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

그들이 환경 변수라는 것을 확신시키기는 조금 더 어렵습니다. 이것을 실행하면 나를 위해 작동합니다.

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

첫 번째 env | grep는 소문자가 하나 인 환경 변수가 없음을 보여줍니다. 첫 번째 bash는를 통해 실행 된 스크립트에 전달 된 인수가 없으며 -c환경에 세 개의 단일 문자 변수가 포함되어 있음을 보여줍니다 . 는 set +k을 취소 -k하고 같은 명령을 지금에 전달 된 인수를 가지고 있음을 보여줍니다. (이것은 대본 a=1과 같이 취급 $0되었으므로 적절한 에코로 증명할 수도 있습니다.)

이것은 타이핑 ./script.sh a=1 b=2이 타이핑과 같아야 한다는 질문을합니다 a=1 b=2 ./script.sh.

스크립트 내에서 다음과 같은 트릭을 시도하면 문제가 발생합니다.

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

"$@"그대로 처리됩니다; 할당 스타일 변수를 찾기 위해 재분석되지 않습니다 ( bash및 모두 ksh). 나는 시도했다 :

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

already_invoked_with_minus_k환경 변수 만 exec'd 스크립트에 설정됩니다 .


아주 좋은 답변입니다! HOME이 변경 가능하기 때문에 이것이 PATH를 변경하지 않는다는 것이 흥미 롭습니다.이 방법으로 설정하기에는 너무 위험한 env var의 블랙리스트 (적어도 PATH 포함)가 있어야합니다. 나는 이것이 매우 짧고 질문에 대답하는 방법을 좋아하지만 sanitize + eval + prefix 솔루션을 사용하여 더 안전하고 보편적으로 사용할 수 있기 때문에 (사용자가 환경을 망칠 필요가없는 환경에서) ). 감사합니다 +1.
PSkocik 2016 년

2

내 시도 :

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

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|

첫 번째 비 X = Y 매개 변수에서 루프를 중단하지 않으면 변화는 잘못된 일을 죽일 것이다
frostschutz

@frostschutz 좋은 지적, 편집했다.
lcd047

그것을 일반화하는 좋은 일. 나는 그것이 조금 단순화 될 수 있다고 생각합니다.
PSkocik 2016 년

내 편집 내용을 살펴볼 기회가 있었습니까?
PSkocik 2016 년

내 편집 내용을 살펴보십시오. 그것이 내가 좋아하는 방식입니다 (+ shift대신에 shift 1). 그렇지 않으면 감사합니다!
PSkocik 2016 년

0

얼마 전에 저는 alias이런 종류의 일을 시작했습니다. 다음은 또 다른 대답입니다.


그러나 때때로 그러한 진술의 평가와 실행을 분리하는 것이 가능할 수 있습니다. 예를 들어 alias명령을 사전 평가하는 데 사용할 수 있습니다. 다음 예에서 변수 정의는 $var평가 중인 변수에 ASCII 영숫자 또는 _와 일치하지 않는 바이트가 포함되어 있지 않은 경우에만 선언 할 수있는 별명에 저장됩니다 .

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여기 alias에서 인용 된 varname 컨텍스트에서 새 호출을 처리하는 데 사용됩니다 . 그리고 eval이전 alias정의가 성공한 경우에만 호출되며 , 많은 다른 구현이 별칭 이름에 대해 많은 다른 종류의 값을 허용한다는 것을 알고 있지만 아직 완전히 비어있는 쉘을 찾지 못했습니다. .

_$var그러나 별명 내의 정의는에 대한 것이며, 이는 중요한 환경 값을 덮어 쓰지 않도록하기위한 것입니다. 나는 _로 시작하는 주목할만한 환경 값을 알지 못하며 일반적으로 반 개인 선언에 안전한 내기입니다.

어쨌든, 별칭 정의가 성공하면 $var의 값으로 명명 된 별칭을 선언합니다 . 그리고 eval해당를 호출 alias다른 - 그것은 또한 숫자로 시작하지 않는 경우 eval에만 널 인수를 가져옵니다. 따라서 두 조건이 모두 충족되면를 eval호출하고에 alias저장된 변수 정의 alias가 작성되면 새 별칭이 해시 테이블에서 즉시 제거됩니다.


alias이 맥락에서 유용한 것은 작업을 인쇄 할 수 있다는 것입니다. alias요청시 이중 인용 된 셸 실행 명령 을 인쇄합니다 .

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

산출

q='some'"'"'args'"'"'which'"'"'alias'"'"'will'"'"'print'"'"'back'"'"'at'"'"'us'
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.