Bash:コマンドが関数の引数として渡されると引用符が取り除かれます


8

スクリプトにドライランのようなメカニズムを実装しようとしていますが、コマンドが引数として関数に渡されたときに引用符が取り除かれ、予期しない動作が発生する問題に直面しています。

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

挿入された場所に引用符が残っている場合、それは当てはまりません。「評価」も試してみましたが、それほど大きな違いはありません。email_adminでdry_run呼び出しを削除してからスクリプトを実行すると、うまく機能します。


回答:



4

"$@"うまくいくはずです。実際、次の簡単なテストケースで私にとってはうまくいきます。

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


4

これはささいな問題ではありません。Shellは、関数を呼び出す前に引用の削除を実行するため、入力したとおりに関数が引用を再作成することはできません。

ただし、コピーして貼り付けてコマンドを繰り返すことができる文字列を出力できるようにするだけの場合は、次の2つの方法があります。

  • 経由するコマンド文字列を作成し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'\'''

2

それはトリッキーです、あなたは私が見たこの他のアプローチを試すかもしれません:

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を空白または「エコー」に設定するだけで、スクリプトが実行するか、単にエコーします。


0

あなたがサポートにbashの最近の十分なを持っている場合ニース挑戦:)それは「簡単」であるべき$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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.