จะปลอดภัยหรือไม่ที่จะประเมิน $ BASH_COMMAND


11

ฉันทำงานกับเชลล์สคริปต์ที่สร้างคำสั่งที่ซับซ้อนจากตัวแปรเช่นนี้ (ด้วยเทคนิคที่ฉันได้เรียนรู้จาก Bash FAQ ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

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

สคริปต์แบบนี้เพิ่มพารามิเตอร์--do-something "$SOME_ARG"และ--with "$ANOTHER_ARG"การsome_complex_commandหากตัวแปรเหล่านี้ถูกกำหนด จนถึงตอนนี้ก็ใช้งานได้ดี

แต่ตอนนี้ฉันต้องการพิมพ์หรือบันทึกคำสั่งเมื่อฉันใช้งานเช่นเมื่อสคริปต์ของฉันทำงานในโหมดดีบัก ดังนั้นเมื่อสคริปต์ของฉันทำงานsome_complex_command --do-something abc --with defฉันยังต้องการให้คำสั่งนี้อยู่ในตัวแปรเพื่อให้ฉันสามารถบันทึกลงใน syslog

Bash คำถามที่พบบ่อยแสดงให้เห็นถึงเทคนิคในการใช้DEBUGกับดักและ$BASH_COMMANDตัวแปร (ตัวอย่างเช่นสำหรับวัตถุประสงค์ในการแก้จุดบกพร่อง) สำหรับวัตถุประสงค์นี้ ฉันลองด้วยรหัสต่อไปนี้:

#!/bin/bash

ARG="test string"

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

echo "Command was: ${COMMAND}"

ใช้งานได้ แต่ไม่ขยายตัวแปรในคำสั่ง:

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

ฉันเดาว่าฉันต้องใช้ eval เพื่อขยายecho "$ARG"ไปยังecho test string(อย่างน้อยฉันก็ยังไม่พบวิธีที่จะevalทำได้) ต่อไปนี้ใช้งานได้:

eval echo "Command was: ${COMMAND}"

มันสร้างผลลัพธ์ต่อไปนี้:

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

แต่ฉันไม่แน่ใจถ้าฉันสามารถใช้evalอย่างปลอดภัยเช่นนี้ ฉันพยายามหาประโยชน์บางอย่างไม่สำเร็จ:

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

ดูเหมือนว่าจะจัดการกับเรื่องนี้ได้ดี แต่ฉันอยากรู้ว่าคนอื่นเห็นปัญหาที่ฉันพลาดหรือไม่


นี่คือสิ่งที่ฉันใช้มานานหลายปีในเสื้อคลุมซูโดฉันไม่เคยสังเกตเห็นปัญหา
w00t

คำตอบ:


7

ความเป็นไปได้อย่างหนึ่งคือสร้างฟังก์ชั่น wrapper ซึ่งจะพิมพ์คำสั่งและดำเนินการในเวลาเดียวกันดังนี้:

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!
    "$@"
}

ดังนั้นในสคริปต์ของคุณคุณสามารถทำได้:

debug echo "$ARG"

ไม่ต้องเล่นซอกับดัก ข้อเสียเปรียบก็คือว่ามันจะเพิ่มdebugคำหลักบางส่วนทั่วรหัสของคุณ (แต่ควรจะดีก็เป็นเรื่องปกติที่จะมีสิ่งต่าง ๆ เช่นยืนยันเช่น ฯลฯ )

คุณสามารถเพิ่มตัวแปรส่วนกลางDEBUGและปรับเปลี่ยนdebugฟังก์ชั่นดังนี้

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!
    "$@"
}

จากนั้นคุณสามารถเรียกสคริปต์ของคุณเป็น:

$ DEBUG=yes ./myscript

หรือ

$ DEBUG= ./myscript

หรือเพียงแค่

$ ./myscript

ขึ้นอยู่กับว่าคุณต้องการมีข้อมูลการดีบักหรือไม่

ฉันใช้ตัวพิมพ์ใหญ่เป็นตัวพิมพ์ใหญ่DEBUGเพราะควรถือว่าเป็นตัวแปรสภาพแวดล้อม DEBUGเป็นชื่อที่ไม่สำคัญและเป็นเรื่องธรรมดาดังนั้นสิ่งนี้อาจขัดแย้งกับคำสั่งอื่น ๆ อาจจะเรียกมันว่าGNIOURF_DEBUGหรือMARTIN_VON_WITTICH_DEBUGหรือUNICORN_DEBUGถ้าคุณชอบยูนิคอร์น (และคุณก็อาจจะชอบม้าด้วย)

บันทึก. ในdebugฟังก์ชั่นนั้นฉันทำการฟอร์แมทแต่ละอาร์กิวเมนต์ด้วยความระมัดระวังprintf '%q'เพื่อให้เอาท์พุตถูกต้องและยกมาอย่างถูกต้องเพื่อให้สามารถนำคำต่อคำซ้ำมาใช้ซ้ำได้ด้วยการคัดลอกและวางโดยตรง นอกจากนี้ยังจะแสดงให้คุณเห็นอย่างชัดเจนถึงสิ่งที่เปลือกหอยเห็นในขณะที่คุณสามารถเข้าใจแต่ละอาร์กิวเมนต์ (ในกรณีที่มีช่องว่างหรือสัญลักษณ์ตลกอื่น ๆ ) ฟังก์ชั่นนี้ยังใช้การกำหนดโดยตรงกับ-vสวิตช์printfเพื่อหลีกเลี่ยง subshells ที่ไม่จำเป็น


1
ฟังก์ชั่นการทำงานที่ดี แต่น่าเสียดายที่คำสั่งของฉันมีการเปลี่ยนเส้นทางและ "excute ในพื้นหลัง" &ผู้ประกอบการควบคุม ฉันต้องย้ายสิ่งเหล่านั้นไปที่ฟังก์ชั่นเพื่อให้มันทำงาน - ไม่ดีมาก แต่ฉันไม่คิดว่าจะมีวิธีที่ดีกว่า แต่ไม่มากevalดังนั้นฉันได้มีที่ไปสำหรับผมซึ่งเป็นสิ่งที่ดี :)
มาร์ตินฟอน Wittich

5

eval "$BASH_COMMAND" รันคำสั่ง

printf '%s\n' "$BASH_COMMAND" พิมพ์คำสั่งที่ระบุแน่นอนรวมถึงการขึ้นบรรทัดใหม่

หากคำสั่งมีตัวแปร (เช่นถ้าเป็นคล้ายcat "$foo") ดังนั้นการพิมพ์คำสั่งจะพิมพ์ข้อความตัวแปร มันเป็นไปไม่ได้ที่จะพิมพ์ค่าของตัวแปรได้โดยไม่ต้องดำเนินการคำสั่ง - variable=$(some_function) other_variable=$variableคิดว่าคำสั่งเช่น

วิธีที่ง่ายที่สุดในการรับการติดตามจากการเรียกใช้งานเชลล์สคริปต์คือการตั้งค่าxtraceตัวเลือกเชลล์โดยเรียกใช้สคริปต์ในฐานะbash -x /path/to/scriptหรือเรียกใช้set -xภายในเชลล์ การติดตามถูกพิมพ์ไปที่ข้อผิดพลาดมาตรฐาน


1
ฉันรู้เรื่องxtraceนี้ แต่นั่นไม่ได้ทำให้ฉันสามารถควบคุมได้มากนัก ฉันลอง "set -x; ... ; set + x" แต่: 1) คำสั่ง "set + x" เพื่อปิดการใช้งาน xtrace ถูกพิมพ์ด้วย 2) ฉันไม่สามารถใส่คำนำหน้าเอาท์พุทเช่นด้วย timestamp 3) ไม่ต้องบันทึกลงใน syslog
Martin von Wittich

2
"มันเป็นไปไม่ได้ที่จะพิมพ์ค่าของตัวแปรโดยไม่ต้องดำเนินการคำสั่ง" - ตัวอย่างที่ดีฉันไม่ได้พิจารณากรณีดังกล่าว มันจะดี แต่ถ้าทุบตีมีตัวแปรที่เพิ่มขึ้นต่อไปในการBASH_COMMANDที่มีคำสั่งขยายเพราะในบางจุดก็มีการขยายตัวที่จะทำตัวแปรคำสั่งต่อไปเมื่อมันรันมัน :)
มาร์ตินฟอน Wittich
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.