Có an toàn khi đánh giá $ BASH_COMMAND không?


11

Tôi đang làm việc với một tập lệnh shell xây dựng một lệnh phức tạp từ các biến, ví dụ như thế này (với một kỹ thuật mà tôi đã học được từ Câu hỏi thường gặp của Bash ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}

Kịch bản này tự động thêm các thông số --do-something "$SOME_ARG"--with "$ANOTHER_ARG"để some_complex_commandnếu các biến được định nghĩa. Cho đến nay điều này là làm việc tốt.

Nhưng bây giờ tôi cũng muốn có thể in hoặc ghi nhật ký lệnh khi tôi đang chạy nó, ví dụ như khi tập lệnh của tôi chạy ở chế độ gỡ lỗi. Vì vậy, khi tập lệnh của tôi chạy some_complex_command --do-something abc --with def, tôi cũng muốn có lệnh này bên trong một biến để tôi có thể đăng nhập nó vào syslog.

Câu hỏi thường gặp về Bash thể hiện một kỹ thuật sử dụng DEBUGbẫy và $BASH_COMMANDbiến (ví dụ cho mục đích gỡ lỗi) cho mục đích này. Tôi đã thử điều đó với đoạn mã sau:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"

Điều này hoạt động, nhưng nó không mở rộng các biến trong lệnh:

host ~ # ./test.sh
test string
Command was: echo "$ARG"

Tôi đoán tôi phải sử dụng eval để mở rộng echo "$ARG"tới echo test string(ít nhất tôi đã không tìm thấy một cách mà không evalđược nêu ra). Sau đây không hoạt động:

eval echo "Command was: ${COMMAND}"

Nó tạo ra đầu ra sau:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string

Nhưng tôi không thực sự chắc chắn nếu tôi có thể sử dụng evalmột cách an toàn như thế này. Tôi đã không thành công khi cố gắng khai thác một số thứ:

#!/bin/bash

ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER

echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"

Có vẻ như xử lý việc này tốt, nhưng tôi tò mò nếu ai đó thấy vấn đề mà tôi đã bỏ qua.


Đây là những gì tôi đã sử dụng trong nhiều năm trong một gói sudo, tôi chưa bao giờ nhận thấy một vấn đề.
w00t

Câu trả lời:


7

Một khả năng là tạo một hàm bao bọc đồng thời in lệnh và thực thi nó, như sau:

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}

Vì vậy, trong kịch bản của bạn, bạn có thể làm:

debug echo "$ARG"

Không cần phải nghịch ngợm với cái bẫy. Hạn chế là nó bổ sung một số debugtừ khóa trên toàn bộ mã của bạn (nhưng điều đó sẽ ổn, thường có những thứ như vậy, như khẳng định, v.v.).

Bạn thậm chí có thể thêm một biến toàn cục DEBUGvà sửa đổi debughàm như vậy:

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}

Sau đó, bạn có thể gọi kịch bản của bạn là:

$ DEBUG=yes ./myscript

hoặc là

$ DEBUG= ./myscript

hoặc chỉ

$ ./myscript

tùy thuộc vào việc bạn có muốn có thông tin gỡ lỗi hay không.

Tôi viết hoa DEBUGbiến vì nó nên được coi là biến môi trường. DEBUGlà một tên tầm thường và phổ biến, vì vậy điều này có thể xung đột với các lệnh khác. Có thể gọi nó GNIOURF_DEBUGhoặc MARTIN_VON_WITTICH_DEBUGhoặc UNICORN_DEBUGnếu bạn thích kỳ lân (và sau đó bạn có thể cũng thích ngựa con).

Ghi chú. Trong debughàm, tôi cẩn thận định dạng từng đối số printf '%q'để đầu ra sẽ được thoát và trích dẫn chính xác để có thể sử dụng lại nguyên văn với một bản sao trực tiếp và dán. Nó cũng sẽ cho bạn thấy chính xác những gì shell đã thấy khi bạn có thể tìm ra từng đối số (trong trường hợp khoảng trắng hoặc các biểu tượng ngộ nghĩnh khác). Chức năng này cũng sử dụng gán trực tiếp với công -vtắc printfđể tránh các subshells không cần thiết.


1
Hàm này hoạt động rất tốt, nhưng thật không may, lệnh của tôi chứa các chuyển hướng và toán tử điều khiển "bài tập trong nền" &. Tôi đã phải chuyển chúng sang chức năng để làm cho nó hoạt động - không hay lắm, nhưng tôi không nghĩ có cách nào tốt hơn. Nhưng không còn nữa eval, vì vậy tôi đã có được điều đó cho tôi, điều đó thật tuyệt :)
Martin von Wittich

5

eval "$BASH_COMMAND" chạy lệnh.

printf '%s\n' "$BASH_COMMAND" in lệnh được chỉ định chính xác, cộng với một dòng mới.

Nếu lệnh chứa các biến (nghĩa là nếu nó giống như vậy cat "$foo"), thì in ra lệnh sẽ in ra văn bản biến. Không thể in giá trị của biến mà không thực hiện lệnh - nghĩ về các lệnh như thế nào variable=$(some_function) other_variable=$variable.

Cách đơn giản nhất để có được dấu vết từ việc thực thi tập lệnh shell là đặt xtracetùy chọn shell bằng cách chạy tập lệnh dưới dạng bash -x /path/to/scripthoặc gọi set -xbên trong trình bao. Các dấu vết được in lỗi tiêu chuẩn.


1
Tôi biết xtrace, nhưng điều đó không cho tôi nhiều quyền kiểm soát. Tôi đã thử "set -x; ...; set + x", nhưng: 1) lệnh "set + x" để vô hiệu hóa xtrace được in quá 2) Tôi không thể đặt tiền tố đầu ra, ví dụ như với dấu thời gian 3) Tôi có thể Đăng nhập vào syslog.
Martin von Wittich

2
"Không thể in giá trị của biến mà không thực hiện lệnh" - ví dụ hay, tôi đã không xem xét trường hợp như vậy. Mặc dù vậy, thật tuyệt nếu bash có một biến bổ sung bên cạnh BASH_COMMANDcó chứa lệnh được mở rộng, bởi vì tại một số điểm, nó phải thực hiện mở rộng biến trên lệnh khi nó thực thi nó :)
Martin von Wittich
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.