Làm thế nào để bắt lỗi trong tập lệnh bash linux?


13

Tôi đã thực hiện đoạn script sau:

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

Kịch bản hoạt động, nhưng bên cạnh tiếng vang của tôi, cũng có đầu ra khi

cd $1

thất bại khi thực hiện.

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

Có thể bắt được điều này?


Chỉ cần một FYI, bạn cũng có thể làm điều này đơn giản hơn rất nhiều; test -d /path/to/directory(hoặc [[ -d /path/to/directory ]]trong bash) sẽ cho bạn biết liệu một mục tiêu đã cho có phải là một thư mục hay không và nó sẽ thực hiện nó một cách lặng lẽ.
Patrick

@Patrick, chỉ kiểm tra xem đó có phải là thư mục không, nếu bạn có thể cdvào đó.
Stéphane Chazelas

@StephaneChazelas có. Tên chức năng là directoryExists.
Patrick

Xem câu trả lời chi tiết tại đây: Tăng lỗi trong tập lệnh Bash .
codeforester

Câu trả lời:


8

Tập lệnh của bạn thay đổi các thư mục khi nó chạy, điều đó có nghĩa là nó sẽ không hoạt động với một loạt các tên đường dẫn tương đối. Sau đó, bạn đã nhận xét rằng bạn chỉ muốn kiểm tra sự tồn tại của thư mục, không phải khả năng sử dụng cd, vì vậy câu trả lời không cần sử dụng cd. Sửa đổi. Sử dụng tput và màu sắc từ 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
}

(Được chỉnh sửa để sử dụng nhiều hơn bất khả xâm phạm printfthay vì vấn đề echocó thể xảy ra đối với các chuỗi thoát trong văn bản.)


Điều đó cũng khắc phục (trừ khi xpg_echo bật) các vấn đề khi tên tệp chứa ký tự dấu gạch chéo ngược.
Stéphane Chazelas

12

Sử dụng set -eđể đặt chế độ thoát lỗi: nếu một lệnh đơn giản trả về trạng thái khác không (biểu thị lỗi), trình bao thoát ra.

Ghi chú rằng set -ekhông phải lúc nào kick trong. Commands ở các vị trí kiểm tra được phép thất bại (ví dụ if failing_command, failing_command || fallback). Các lệnh trong subshell chỉ dẫn đến thoát khỏi subshell, không phải cha mẹ: set -e; (false); echo foohiển thị foo.

Ngoài ra, hoặc ngoài ra, trong bash (và ksh và zsh, nhưng không phải sh đơn giản), bạn có thể chỉ định một lệnh được thực thi trong trường hợp lệnh trả về trạng thái khác ERR, ví dụ như bẫy trap 'err=$?; echo >&2 "Exiting on error $err"; exit $err' ERR. Lưu ý rằng trong các trường hợp như (false); …, bẫy ERR được thực thi trong lớp con, vì vậy nó không thể khiến cha mẹ thoát ra.


Gần đây tôi đã thử nghiệm một chút và phát hiện ra một cách sửa chữa ||hành vi thuận tiện , cho phép dễ dàng thực hiện xử lý lỗi thích hợp mà không cần sử dụng bẫy. Xem câu trả lời của tôi . Bạn nghĩ gì về phương pháp đó?
skozin

@ sam.kozin Tôi không có thời gian để xem xét chi tiết câu trả lời của bạn, nó có vẻ tốt về nguyên tắc. Ngoài tính di động, những lợi ích so với bẫy ERR của ksh / bash / zsh là gì?
Gilles 'SO- ngừng trở nên xấu xa'

Có lẽ lợi ích duy nhất là khả năng kết hợp, vì bạn không có nguy cơ ghi đè lên một cái bẫy khác đã được đặt trước khi bạn hoạt động. Đây là một tính năng hữu ích khi bạn viết một số chức năng phổ biến mà sau này bạn sẽ lấy và sử dụng từ các tập lệnh khác. Một lợi ích khác có thể là khả năng tương thích POSIX đầy đủ, mặc dù điều đó không quá quan trọng vì ERRtín hiệu giả được hỗ trợ trong tất cả các hệ vỏ chính. Cảm ơn đã xem xét! =)
skozin

@ sam.kozin Tôi quên viết trong bình luận trước đây của mình: bạn có thể muốn đăng bài này lên Đánh giá mã và đăng một liên kết trong phòng chat .
Gilles 'SO- ngừng trở nên xấu xa'

Cảm ơn lời đề nghị, tôi sẽ cố gắng làm theo nó. Không biết về Đánh giá mã.
skozin

5

Để mở rộng câu trả lời của @Gilles :

Thật vậy, set -ekhông hoạt động bên trong các lệnh nếu bạn sử dụng ||toán tử sau chúng, ngay cả khi bạn chạy chúng trong một lớp con; ví dụ, điều này sẽ không hoạt động:

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

Nhưng người ||vận hành là cần thiết để ngăn trở về từ chức năng bên ngoài trước khi dọn dẹp.

Có một mẹo nhỏ có thể được sử dụng để khắc phục điều này: chạy lệnh bên trong trong nền, sau đó ngay lập tức chờ đợi nó. Nội dung waitsẽ trả về mã thoát của lệnh bên trong và bây giờ bạn đang sử dụng hàm ||sau wait, không phải hàm bên trong, do đó set -ehoạt động chính xác bên trong lệnh sau:

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

Đây là chức năng chung dựa trên ý tưởng này. Nó sẽ hoạt động trong tất cả các shell tương thích POSIX nếu bạn xóa localtừ khóa, tức là thay thế tất cả local x=ychỉ bằng 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
}

Ví dụ về cách sử dụng:

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

Chạy ví dụ:

$ ./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

Điều duy nhất mà bạn cần lưu ý khi sử dụng phương thức này là tất cả các sửa đổi của các biến Shell được thực hiện từ lệnh bạn truyền đến runsẽ không truyền đến hàm gọi, bởi vì lệnh chạy trong một lớp con.


2

Bạn không nói chính xác ý của bạn bằng cách catch--- báo cáo và tiếp tục; hủy bỏ tiếp tục xử lý?

cdtrả về trạng thái khác không khi thất bại, bạn có thể làm:

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

Bạn chỉ có thể thoát khỏi thất bại:

cd -- "$1" || exit 1

Hoặc, lặp lại tin nhắn của riêng bạn và thoát:

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

Và / hoặc loại bỏ lỗi được cung cấp bởi cdlỗi:

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

Theo tiêu chuẩn, các lệnh sẽ đặt thông báo lỗi trên STDERR (mô tả tệp 2). Do đó 2>/dev/null, chuyển hướng STDERR đến "bit-xô" được biết đến bởi /dev/null.

(đừng quên trích dẫn các biến của bạn và đánh dấu sự kết thúc của các tùy chọn cho cd).


@Stephane Chazelas điểm trích dẫn và báo hiệu kết thúc tùy chọn cũng được thực hiện. Cảm ơn đã chỉnh sửa.
JRFerguson

1

Trên thực tế đối với trường hợp của bạn tôi sẽ nói rằng logic có thể được cải thiện.

Thay vì cd và sau đó kiểm tra nếu nó tồn tại, kiểm tra nếu nó tồn tại sau đó đi vào thư mục.

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

Nhưng nếu mục đích của bạn là để im lặng các lỗi có thể sau đó cd -- "$1" 2>/dev/null, nhưng điều này sẽ khiến bạn gỡ lỗi trong tương lai khó khăn hơn. Bạn có thể kiểm tra các cờ kiểm tra tại: Bash nếu tài liệu :


Câu trả lời này không trích dẫn $1biến và sẽ thất bại nếu biến đó chứa khoảng trắng hoặc các siêu ký tự shell khác. Nó cũng không kiểm tra xem người dùng có được phép cdvào nó hay không.
Ian D. Allen

Tôi đã thực sự cố gắng kiểm tra nếu một thư mục nhất định tồn tại, không nhất thiết phải cd vào nó. Nhưng vì tôi không biết rõ hơn, tôi nghĩ rằng cố gắng cd vào nó sẽ gây ra lỗi nếu không tồn tại, vậy tại sao không bắt nó? Tôi không biết nếu [-d $ 1] chính xác là những gì tôi cần. Vì vậy, cảm ơn bạn rất nhiều! (Tôi đã quen với proram Java và kiểm tra thư mục trong câu lệnh if không chính xác phổ biến trong Java)
Thomas De Wilde
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.