Bash : 명령이 함수에 인수로 전달 될 때 따옴표가 제거됩니다. log -1 -p|mail -s ‘$mail_subject’

스크립트에 드라이 런 종류의 메커니즘을 구현하려고하며 명령이 함수에 인수로 전달되어 예기치 않은 동작이 발생하면 따옴표가 제거되는 문제에 직면하고 있습니다.

dry_run () {
    echo "$@"
    #printf '%q ' "$@"

    if [ "$DRY_RUN" ]; then
        return 0
    fi

    "$@"
}


email_admin() {
    echo " Emailing admin"
    dry_run su - $target_username  -c "cd $GIT_WORK_TREE && git log -1 -p|mail -s '$mail_subject' $admin_email"
    echo " Emailed"
    }

출력은 다음과 같습니다

su - webuser1 -c cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' user@domain.com

예상 :

su - webuser1 -c "cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' user@domain.com"

echo 대신 printf를 활성화 한 경우 :

su - webuser1 -c cd\ /home/webuser1/public_html\ \&\&\ git\ log\ -1\ -p\|mail\ -s\ \'Git\ deployment\ on\ webuser1\'\ user@domain.com

결과:

su: invalid option -- 1

인용 부호가 삽입 된 위치에 남아있는 경우에는 그렇지 않습니다. 나는 또한 “eval”을 사용하여 많은 차이를 보지 못했습니다. email_admin에서 dry_run 호출을 제거한 다음 스크립트를 실행하면 훌륭하게 작동합니다.



답변

\"그냥 대신 사용해보십시오 ".


답변

"$@"작동해야합니다. 실제로이 간단한 테스트 사례에서 저에게 효과적입니다.

dry_run()
{
    "$@"
}

email_admin()
{
    dry_run su - foo -c "cd /var/tmp && ls -1"
}

email_admin

산출:

./foo.sh 
a
b

추가 편집 : 출력 echo $@이 정확합니다. 는 "메타 문자 및 매개 변수의 일부입니다. 에 추가 echo $5하여 올바르게 작동하고 있음을 증명할 수 있습니다 dry_run(). 그것은 후에 모든 것을 출력합니다-c


답변

이것은 사소한 문제가 아닙니다. 셸은 함수를 호출하기 전에 따옴표 제거를 수행하므로 사용자가 입력 한대로 따옴표를 정확하게 다시 만들 수있는 방법은 없습니다.

그러나 명령을 반복하기 위해 복사하여 붙여 넣을 수있는 문자열을 인쇄하려면 두 가지 방법을 사용할 수 있습니다.

  • 실행할 명령 문자열을 작성하고 eval해당 문자열을dry_run
  • dry_run인쇄하기 전에 명령의 특수 문자 인용

사용 eval

eval실행되는 내용을 정확하게 인쇄 하는 방법은 다음과 같습니다 .

dry_run() {
    printf '%s\n' "$1"
    [ -z "${DRY_RUN}" ] || return 0
    eval "$1"
}

email_admin() {
    echo " Emailing admin"
    dry_run 'su - '"$target_username"'  -c "cd '"$GIT_WORK_TREE"' && git log -1 -p|mail -s '"'$mail_subject'"' '"$admin_email"'"'
    echo " Emailed"
}

산출:

su - webuser1  -c "cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' user@domain.com"

따옴표의 미친 양에 주목하십시오-당신은 명령 안에 명령 안에 명령이 있고, 이것은 추악하게 빠릅니다. 주의 사항 : 변수에 공백이나 따옴표와 같은 특수 문자가 포함되어 있으면 위의 코드에 문제가 있습니다.

인용 특수 문자

이 접근 방식을 사용하면 코드를보다 자연스럽게 작성할 수 있지만 더 빠르고 더러워진 방식으로 인해 사람이 읽기 어려운 결과가 나옵니다 shell_quote.

# This function prints each argument wrapped in single quotes
# (separated by spaces).  Any single quotes embedded in the
# arguments are escaped.
#
shell_quote() {
    # run in a subshell to protect the caller's environment
    (
        sep=''
        for arg in "$@"; do
            sqesc=$(printf '%s\n' "${arg}" | sed -e "s/'/'\\\\''/g")
            printf '%s' "${sep}'${sqesc}'"
            sep=' '
        done
    )
}

dry_run() {
    printf '%s\n' "$(shell_quote "$@")"
    [ -z "${DRY_RUN}" ] || return 0
    "$@"
}

email_admin() {
    echo " Emailing admin"
    dry_run su - "${target_username}"  -c "cd $GIT_WORK_TREE && git log -1 -p|mail -s '$mail_subject' $admin_email"
    echo " Emailed"
}

산출:

'su' '-' 'webuser1' '-c' 'cd /home/webuser1/public_html && git log -1 -p|mail -s '\''Git deployment on webuser1'\'' user@domain.com'

shell_quote모든 것을 작은 따옴표로 묶지 않고 백 슬래시 이스케이프 특수 문자 로 변경하여 출력의 가독성을 향상시킬 수 있지만 올바르게 수행하기는 어렵습니다.

이 방법을 사용하면보다 안전한 방식으로 shell_quote전달할 명령을 구성 할 수 있습니다 su. 다음은 작동하더라도 것 ${GIT_WORK_TREE}, ${mail_subject}또는 ${admin_email}특수 문자 (작은 따옴표, 공백, 별표, 세미콜론 등)을 포함 :

email_admin() {
    echo " Emailing admin"
    cmd=$(
        shell_quote cd "${GIT_WORK_TREE}"
        printf '%s' ' && git log -1 -p | '
        shell_quote mail -s "${mail_subject}" "${admin_email}"
    )
    dry_run su - "${target_username}"  -c "${cmd}"
    echo " Emailed"
}

산출:

'su' '-' 'webuser1' '-c' ''\''cd'\'' '\''/home/webuser1/public_html'\'' && git log -1 -p | '\''mail'\'' '\''-s'\'' '\''Git deployment on webuser1'\'' '\''user@domain.com'\'''


답변

까다로워, 내가 본 다른 접근법을 시도해 볼 수 있습니다.

DRY_RUN=
#DRY_RUN=echo
....
email_admin() {
    echo " Emailing admin"
    $DRY_RUN su - $target_username  -c "cd $GIT_WORK_TREE && git log -1 -p|mail -s '$mail_subject' $admin_email"
    echo " Emailed"
    }

그렇게하면 스크립트 상단에 DRY_RUN을 공백 또는 “에코”로 설정하면됩니다.


답변

좋은 도전 🙂 당신이 지원 $LINENO하고 충분히 최근 배쉬가 있다면 그것은 “쉬운”해야$BASH_SOURCE

귀하의 필요에 맞는 첫 번째 시도는 다음과 같습니다.

#!/bin/bash
#adjust the previous line if needed: on prompt, do "type -all bash" to see where it is.    
#we check for the necessary ingredients:
[ "$BASH_SOURCE" = "" ] && { echo "you are running a too ancient bash, or not running bash at all. Can't go further" ; exit 1 ; }
[ "$LINENO" = "" ] && { echo "your bash doesn't support LINENO ..." ; exit 2 ; }
# we passed the tests. 
export _tab_="`printf '\011'`" #portable way to define it. It is used below to ensure we got the correct line, whatever separator (apart from a \CR) are between the arguments

function printandexec {
   [ "$FUNCNAME" = "" ] && { echo "your bash doesn't support FUNCNAME ..." ; exit 3 ; }
   #when we call this, we should do it like so :  printandexec $LINENO / complicated_cmd 'with some' 'complex arguments | and maybe quoted subshells'
   # so : $1 is the line in the $BASH_SOURCE that was calling this function
   #    : $2 is "/" , which we will use for easy cut
   #    : $3-... are the remaining arguments (up to next ; or && or || or | or #. However, we don't care, we use another mechanism...)
   export tmpfile="/tmp/printandexec.$$" #create a "unique" tmp file
   export original_line="$1"
   #1) display & save for execution:
   sed -e "${original_line}q;d" < ${BASH_SOURCE} | grep -- "${FUNCNAME}[ ${_tab_}]*\$LINENO" | cut -d/ -f2- | tee "${tmpfile}"
   #then execute it in the *current* shell so variables, etc are all set correctly:
   source ${tmpfile}
   rm -f "${tmpfile}"; #always have last command in a function finish by ";"

}

echo "we do stuff here:"
printandexec  $LINENO  / ls -al && echo "something else" #and you can even put commentaries!
#printandexec  $LINENO / su - $target_username  -c "cd $GIT_WORK_TREE && git log -1 -p|mail -s '$mail_subject' $admin_email"
#uncommented the previous on your machine once you're confident the script works


답변