Bash: Trích dẫn bị tước khi một lệnh được truyền làm đối số cho hàm


8

Tôi đang cố gắng thực hiện một loại cơ chế chạy khô cho kịch bản của mình và đối mặt với vấn đề trích dẫn bị loại bỏ khi một lệnh được truyền dưới dạng đối số cho hàm và dẫn đến hành vi không mong muốn.

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"
    }

Đầu ra là:

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

Hy vọng:

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

Với printf được kích hoạt thay vì echo:

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

Kết quả:

su: invalid option -- 1

Đó không phải là trường hợp nếu trích dẫn vẫn còn nơi chúng được chèn. Tôi cũng đã thử sử dụng "eval", không có nhiều khác biệt. Nếu tôi loại bỏ cuộc gọi Dry_run trong email_admin và sau đó chạy tập lệnh, nó hoạt động rất tốt.


Câu trả lời:



4

"$@"nên làm việc. Trong thực tế, nó hoạt động với tôi trong trường hợp thử nghiệm đơn giản này:

dry_run()
{
    "$@"
}

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

email_admin

Đầu ra:

./foo.sh 
a
b

Chỉnh sửa để thêm: đầu ra của echo $@là chính xác. Đây "là một siêu ký tự và không phải là một phần của tham số. Bạn có thể chứng minh rằng nó hoạt động chính xác bằng cách thêm echo $5vào dry_run(). Nó sẽ xuất ra mọi thứ sau-c


4

Đây không phải là một vấn đề tầm thường. Shell thực hiện loại bỏ trích dẫn trước khi gọi hàm, vì vậy không có cách nào hàm có thể tạo lại các trích dẫn chính xác như bạn đã gõ chúng.

Tuy nhiên, nếu bạn chỉ muốn có thể in ra một chuỗi có thể được sao chép và dán để lặp lại lệnh, có hai cách tiếp cận khác nhau bạn có thể thực hiện:

  • Xây dựng một chuỗi lệnh được chạy qua evalvà chuyển chuỗi đó đếndry_run
  • Trích dẫn các ký tự đặc biệt của lệnh dry_runtrước khi in

Sử dụng eval

Đây là cách bạn có thể sử dụng evalđể in chính xác những gì đang chạy:

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"
}

Đầu ra:

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

Lưu ý số lượng trích dẫn điên rồ - bạn đã có một lệnh trong một lệnh trong một lệnh, điều này sẽ trở nên xấu đi nhanh chóng. Chú ý: Đoạn mã trên sẽ có vấn đề nếu các biến của bạn chứa khoảng trắng hoặc ký tự đặc biệt (như dấu ngoặc kép).

Trích dẫn nhân vật đặc biệt

Cách tiếp cận này cho phép bạn viết mã một cách tự nhiên hơn, nhưng đầu ra của con người khó đọc hơn vì cách thức nhanh và bẩn shell_quoteđược thực hiện:

# 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"
}

Đầu ra:

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

Bạn có thể cải thiện khả năng đọc của đầu ra bằng cách thay đổi shell_quotethành ký tự đặc biệt dấu gạch chéo ngược thay vì gói mọi thứ trong dấu ngoặc đơn, nhưng thật khó để làm chính xác.

Nếu bạn thực hiện shell_quotephương pháp này, bạn có thể xây dựng lệnh để chuyển đến sumột cách an toàn hơn. Các mục sau sẽ hoạt động ngay cả khi ${GIT_WORK_TREE}, ${mail_subject}hoặc ${admin_email}chứa các ký tự đặc biệt (dấu ngoặc đơn, dấu cách, dấu hoa thị, dấu chấm phẩy, v.v.):

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"
}

Đầu ra:

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

2

Điều đó thật khó khăn, bạn có thể thử cách tiếp cận khác mà tôi đã thấy:

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"
    }

bằng cách đó, bạn chỉ cần đặt DRY_RUN thành trống hoặc "echo" ở đầu tập lệnh của mình và nó sẽ thực hiện hoặc chỉ lặp lại nó.


0

Thử thách thú vị :) Sẽ thật "dễ dàng" nếu bạn có bash gần đây đủ để hỗ trợ $LINENO$BASH_SOURCE

Đây là nỗ lực đầu tiên của tôi, hy vọng nó phù hợp với nhu cầu của bạn:

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