วิธีการจับข้อผิดพลาดในสคริปต์ทุบตีลินุกซ์?


13

ฉันทำสคริปต์ต่อไปนี้:

# !/bin/bash

# OUTPUT-COLORING
red='\e[0;31m'
green='\e[0;32m'
NC='\e[0m' # No Color

# FUNCTIONS
# directoryExists - Does the directory exist?
function directoryExists {
    cd $1
    if [ $? = 0 ]
            then
                    echo -e "${green}$1${NC}"
            else
                    echo -e "${red}$1${NC}"
    fi
}

# EXE
directoryExists "~/foobar"
directoryExists "/www/html/drupal"

สคริปต์ทำงานได้ แต่ข้างก้องของฉันก็มีเอาต์พุตเมื่อ

cd $1

ล้มเหลวในการดำเนินการ

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory

เป็นไปได้ไหมที่จะจับสิ่งนี้?


เพียงแค่ FYI คุณสามารถทำสิ่งนี้ได้ง่ายกว่ามาก test -d /path/to/directory(หรือ[[ -d /path/to/directory ]]ในทุบตี) จะบอกคุณว่าเป้าหมายที่กำหนดเป็นไดเรกทอรีหรือไม่และจะทำอย่างเงียบ ๆ
Patrick

@ แพทริกที่เพิ่งทดสอบถ้ามันไดเรกทอรีไม่ได้ถ้าคุณสามารถcdเป็นมัน
Stéphane Chazelas

@StephaneChazelas ใช่ directoryExistsชื่อฟังก์ชั่น
Patrick

ดูรายละเอียดที่นี่คำตอบ: ข้อผิดพลาดในการยกสคริปต์ทุบตี
codeforester

คำตอบ:


8

สคริปต์ของคุณเปลี่ยนไดเรกทอรีในขณะที่ทำงานซึ่งหมายความว่าจะไม่ทำงานกับชื่อพา ธ แบบสัมพันธ์ จากนั้นคุณแสดงความคิดเห็นในภายหลังว่าคุณเพียงต้องการตรวจสอบการมีอยู่ของไดเรกทอรีไม่ใช่ความสามารถในการใช้ cdดังนั้นคำตอบไม่จำเป็นต้องใช้cdเลย แก้ไข การใช้tput และสีจากman terminfo:

#!/bin/bash -u
# OUTPUT-COLORING
red=$( tput setaf 1 )
green=$( tput setaf 2 )
NC=$( tput setaf 0 )      # or perhaps: tput sgr0

# FUNCTIONS
# directoryExists - Does the directory exist?
function directoryExists {
    # was: do the cd in a sub-shell so it doesn't change our own PWD
    # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then
    if [ -d "$1" ] ; then
        # was: echo "${green}$1${NC}"
        printf "%s\n" "${green}$1${NC}"
    else
        # was: echo "${red}$1${NC}"
        printf "%s\n" "${red}$1${NC}"
        # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}"
    fi
}

(แก้ไขเพื่อใช้ความปลอดภัยมากกว่าprintfแทนที่จะเป็นปัญหา echoที่อาจทำตามลำดับการหลีกเลี่ยงในข้อความ)


นอกจากนี้ยังแก้ไข (เว้นแต่ว่า xpg_echo เปิดอยู่) ปัญหาเมื่อชื่อไฟล์มีอักขระเครื่องหมายทับขวา
Stéphane Chazelas

12

ใช้set -eเพื่อตั้งค่าโหมด exit-on-error: หากคำสั่งง่าย ๆ ส่งคืนสถานะที่ไม่ใช่ศูนย์ (ระบุความล้มเหลว) เชลล์จะออก

ระวังว่าset -eไม่เคยเตะใน. คำสั่งในตำแหน่งการทดสอบจะได้รับอนุญาตที่จะล้มเหลว (เช่นif failing_command, failing_command || fallback) คำสั่งใน subshell เพียงนำไปสู่การออก subshell ไม่แม่: แสดงset -e; (false); echo foofoo

หรืออีกวิธีหนึ่งหรือในนอกจากนี้ในทุบตี (และ ksh และ zsh แต่ดวลจุดโทษไม่ธรรมดา) คุณสามารถระบุคำสั่งที่ดำเนินการในกรณีที่คำสั่งคืนค่าสถานะที่ไม่ใช่ศูนย์ที่กับดักเช่นERR trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERRโปรดทราบว่าในกรณีเช่น(false); …นี้กับดัก ERR จะดำเนินการใน subshell ดังนั้นจึงไม่สามารถทำให้ผู้ปกครองออกจาก


เมื่อเร็ว ๆ นี้ฉันทำการทดลองเล็กน้อยและค้นพบวิธีที่สะดวกในการแก้ไข||พฤติกรรมซึ่งช่วยให้สามารถจัดการข้อผิดพลาดที่เหมาะสมได้อย่างง่ายดายโดยไม่ต้องใช้กับดัก ดูคำตอบของฉัน คุณคิดอย่างไรเกี่ยวกับวิธีการนั้น
skozin

@ sam.kozin ฉันไม่มีเวลาทบทวนรายละเอียดคำตอบของคุณมันดูดีในหลักการ นอกเหนือจากการพกพาแล้วประโยชน์ของกับดัก ERR ของ ksh / bash / zsh คืออะไร?
Gilles 'SO- หยุดความชั่วร้าย'

อาจเป็นประโยชน์เพียงอย่างเดียวคือความสามารถในการจัดเรียงเนื่องจากคุณไม่เสี่ยงที่จะเขียนทับกับดักอื่นที่ตั้งค่าไว้ก่อนที่คุณจะทำงานได้ ซึ่งเป็นคุณสมบัติที่มีประโยชน์เมื่อคุณกำลังเขียนฟังก์ชั่นทั่วไปบางอย่างที่คุณจะมาในภายหลังและใช้จากสคริปต์อื่น ๆ ข้อดีอีกประการคือความเข้ากันได้ของ POSIX เต็มรูปแบบแม้ว่ามันจะไม่สำคัญเท่าไรเพราะERRสัญญาณหลอกได้รับการสนับสนุนในเชลล์หลักทั้งหมด ขอบคุณสำหรับรีวิว! =)
skozin

@ sam.kozin ฉันลืมที่จะเขียนในความคิดเห็นก่อนหน้าของฉัน: คุณอาจต้องการที่จะโพสต์นี้ในรหัสตรวจสอบและโพสต์ลิงค์ในการสนทนา
Gilles 'หยุดความชั่วร้าย'

ขอบคุณสำหรับคำแนะนำฉันจะพยายามติดตาม ไม่ทราบเกี่ยวกับการตรวจสอบรหัส
skozin

6

หากต้องการขยายคำตอบของ @Gilles :

อันที่จริงset -eไม่ทำงานภายในคำสั่งถ้าคุณใช้||ผู้ประกอบการหลังจากพวกเขาแม้ว่าคุณจะเรียกใช้พวกเขาใน subshell; เช่นนี้ใช้งานไม่ได้:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer

set -e

outer() {
  echo '--> outer'
  (inner) || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

แต่||จำเป็นต้องใช้ตัวดำเนินการเพื่อป้องกันการส่งคืนจากฟังก์ชันภายนอกก่อนที่จะล้าง

มีเคล็ดลับเล็กน้อยที่สามารถใช้ในการแก้ไขปัญหานี้: เรียกใช้คำสั่งด้านในเป็นพื้นหลังแล้วรอทันที waitbuiltin จะกลับรหัสทางออกของคำสั่งภายในและตอนนี้คุณกำลังใช้||หลังจากที่waitไม่ได้ฟังก์ชั่นด้านในเพื่อให้set -eทำงานอย่างถูกต้องภายในหลัง:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup

set -e

outer() {
  echo '--> outer'
  inner &
  wait $! || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

นี่คือฟังก์ชั่นทั่วไปที่สร้างขึ้นจากความคิดนี้ มันควรจะทำงานในเชลล์ที่เข้ากันได้กับ POSIX ทั้งหมดหากคุณลบlocalคำหลักออกนั่นคือแทนที่ทั้งหมดlocal x=yด้วยเพียงx=y:

# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
  local cmd="$1"; shift
  local exit_code=0

  local e_was_set=1; if ! is_shell_attribute_set e; then
    set -e
    e_was_set=0
  fi

  "$cmd" "$@" &

  wait $! || {
    exit_code=$?
  }

  if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
    set +e
  fi

  if [ -n "$CLEANUP" ]; then
    RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
    return $?
  fi

  return $exit_code
}


is_shell_attribute_set() { # attribute, like "x"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}

ตัวอย่างการใช้งาน:

#!/bin/sh
set -e

# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh


main() {
  echo "--> main: $@"
  CLEANUP=cleanup run inner "$@"
  echo "<-- main"
}


inner() {
  echo "--> inner: $@"
  sleep 0.5; if [ "$1" = 'fail' ]; then
    oh_my_god_look_at_this
  fi
  echo "<-- inner"
}


cleanup() {
  echo "--> cleanup: $@"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "$@"

ใช้ตัวอย่าง:

$ ./so_3 fail; echo "exit code: $?"

--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127

$ ./so_3 pass; echo "exit code: $?"

--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0

สิ่งเดียวที่คุณต้องระวังเมื่อใช้วิธีนี้คือการแก้ไขตัวแปร Shell ทั้งหมดที่ทำจากคำสั่งที่คุณส่งไปrunจะไม่แพร่กระจายไปยังฟังก์ชันการเรียกใช้เนื่องจากคำสั่งทำงานในเชลล์ย่อย


2

คุณไม่ได้พูดในสิ่งที่คุณหมายถึงโดยcatch--- รายงานและดำเนินการต่อ; ยกเลิกการประมวลผลเพิ่มเติมหรือไม่

เนื่องจากcdส่งคืนสถานะที่ไม่เป็นศูนย์เมื่อเกิดความล้มเหลวคุณสามารถทำ:

cd -- "$1" && echo OK || echo NOT_OK

คุณสามารถออกจากเมื่อล้มเหลว:

cd -- "$1" || exit 1

หรือแสดงข้อความของคุณเองและออก:

cd -- "$1" || { echo NOT_OK; exit 1; }

และ / หรือระงับข้อผิดพลาดที่เกิดจากcdความล้มเหลว:

cd -- "$1" 2>/dev/null || exit 1

ตามมาตรฐานคำสั่งควรใส่ข้อความแสดงข้อผิดพลาดบน STDERR (ตัวอธิบายไฟล์ 2) ดังนั้นจึง2>/dev/nullกล่าวว่า STDERR เปลี่ยนเส้นทางไปยัง "บิตถัง" /dev/nullที่รู้จักกันโดย

(อย่าลืมอ้างอิงตัวแปรและทำเครื่องหมายที่ตัวเลือกสุดท้ายcd)


@Stephane Chazelas เป็นจุดอ้างอิงและการส่งสัญญาณการสิ้นสุดของตัวเลือกที่ได้รับอย่างดี ขอบคุณสำหรับการแก้ไข
JRFerguson

1

จริงๆแล้วสำหรับกรณีของคุณฉันจะบอกว่าตรรกะสามารถปรับปรุงได้

แทนที่จะเป็น cd แล้วตรวจสอบว่ามีอยู่หรือไม่ให้ตรวจสอบว่ามีอยู่แล้วไปที่ไดเรกทอรี

if [ -d "$1" ]
then
     printf "${green}${NC}\\n" "$1"
     cd -- "$1"
else 
     printf "${red}${NC}\\n" "$1"
fi  

แต่ถ้าจุดประสงค์ของคุณคือการหยุดข้อผิดพลาดที่เป็นไปได้cd -- "$1" 2>/dev/nullแต่สิ่งนี้จะทำให้คุณแก้ไขข้อบกพร่องในอนาคตได้ยากขึ้น คุณสามารถตรวจสอบการทดสอบการตั้งค่าสถานะที่: Bash ถ้าเอกสารประกอบ :


คำตอบนี้ล้มเหลวในการอ้างอิง$1ตัวแปรและจะล้มเหลวหากตัวแปรนั้นมีช่องว่างหรือเชลล์ตัวอักขระอื่น ๆ นอกจากนี้ยังล้มเหลวในการตรวจสอบว่าผู้ใช้มีสิทธิ์cdในมัน
Ian D. Allen

ฉันพยายามตรวจสอบว่ามีไดเรกทอรีอยู่จริงหรือไม่ แต่เพราะฉันไม่รู้ดีกว่าฉันคิดว่าการลองซีดีมันจะทำให้เกิดข้อผิดพลาดหากไม่มีอยู่แล้วทำไมไม่ลองดูล่ะ ฉันไม่รู้ว่า [-d $ 1] นั้นเป็นสิ่งที่ฉันต้องการหรือไม่ ขอบคุณมาก! (ฉันใช้ในการ proram Java, และการตรวจสอบไดเรกทอรีในงบถ้าไม่ตรงที่พบบ่อยใน Java)
โทมัสเดอร์ไวลด์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.