Ống / chuyển hướng một nhóm các lệnh


8

Tôi hiện đang sử dụng thiết lập sau để chuyển hướng đầu ra của nhiều lệnh:

echo "Some normal commands"
(
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"

Điều này khá hữu ích, và nó cũng hoạt động với đường ống.

Đây có phải là cách tốt nhất để làm điều này? Có một sự thay thế mà tôi nên xem xét?


Đó là cách tôi làm. Bạn đang gặp phải một vấn đề cụ thể với phương pháp này?
Bratchley

@JoelDavis Không, chỉ tự hỏi liệu có cách nào tốt hơn để làm điều đó. Từ những câu trả lời tôi nhận được, có vẻ như là có! :)
wchargein

Câu trả lời:


15

Thay thế là sử dụng niềng răng thay vì dấu ngoặc đơn. Thay đổi này thực thi các lệnh trong shell hiện tại , không phải trong một lớp con

echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"

ref: https://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

Điều này đặc biệt có liên quan khi bạn sửa đổi các biến trong nhóm:

$ x=5; ( x=10; echo inside: $x; ); echo outside: $x
inside: 10
outside: 5

$ x=5; { x=10; echo inside: $x; }; echo outside: $x
inside: 10
outside: 10

Cảm ơn vô cùng! Điều này làm việc tốt hơn với thụt đầu dòng của tôi là tốt. (Vim thụt vào { }nhưng không ( ).)
wchargein

2
Lưu ý rằng bạn có thể thu gọn nhóm lệnh thành một dòng duy nhất; ví dụ: (echo msg1; echo msg2)- nhưng, với dấu ngoặc nhọn, nó phải là { echo msg1; echo msg2;}, với khoảng trắng sau {dấu chấm phẩy ( ;) hoặc dấu và ( &trước dấu ) }.
G-Man nói 'Phục hồi Monica'

4

Câu trả lời của Glenn là một câu trả lời hay - sự khác biệt giữa ( ... ){ ... }quan trọng.

Một chiến lược tôi thường sử dụng cho đầu ra lỗi như những gì trong câu hỏi của bạn là teelệnh. Bạn có thể làm một cái gì đó như thế này:

echo "Normal output"
{
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "Warning text"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "This event is logged."
} | tee -a $logfile >&2
echo "More normal output"

Các teelệnh sẽ gửi ra hai nơi; -atùy chọn "nối" đầu ra vào tệp đã đặt tên và lệnh cũng sẽ chuyển đầu vào cùng với thiết bị xuất chuẩn. Phần >&2cuối của dòng chuyển hướng teecủa stdout đến stderr, có thể được xử lý khác nhau (nghĩa là trong một công việc định kỳ).

Một mẹo khác mà tôi thường sử dụng trong các kịch bản shell là thay đổi hành vi của đầu ra gỡ lỗi hoặc dài dòng dựa trên việc tập lệnh đang chạy trên thiết bị đầu cuối hay có -vtùy chọn được cung cấp. Ví dụ:

#!/bin/sh

# Set defaults
if [ -t 0 ]; then
  Verbose=true; vflag="-v"
else
  Verbose=false; vflag=""
fi
Debug=false; AskYN=true; Doit=true

# Detect options (altering defaults)
while getopts vdqbn opt; do
  case "$opt" in
    v)  Verbose=true; vflag="-v" ;;             # Verbose mode
    d)  Debug=true; Verbose=true; vflag="-v" ;; # Very Verbose
    q)  Verbose=false; vflag="" ;;              # quiet mode (non-verbose)
    b)  AskYN=false ;;                          # batch mode
    n)  Doit=false ;;                           # test mode
    *)  usage; exit 1 ;;
  esac
done

# Shift our options for further processing
shift $(($OPTIND - 1))

$Verbose && echo "INFO: Verbose output is turned on." >&2
$Debug && echo "INFO: In fact, expect to be overrun." >&2

# Do your thing here
if $AskYN; then
  read -p "Continue? " choice
  case "$choice" in
    Y|y) $Doit && somecommand ;;
    *) echo "Done." ;;
  esac
fi

Các tập lệnh có thể bắt đầu với một cái gì đó chung chung như thế này ở đầu, với đầu ra Verbose và Debug nằm rải rác trong tập lệnh. Đó chỉ là một cách để làm điều đó - có rất nhiều, và những người khác nhau sẽ có cách riêng để xử lý công cụ này, đặc biệt là nếu họ đã ở đây một thời gian. :)

Thêm một lựa chọn nữa là xử lý đầu ra của bạn bằng "handler" - một hàm shell có thể làm những việc thông minh hơn. Ví dụ:

#!/bin/bash

logme() {
  case "${1^^}" in
    [IN]*)  level=notice ;;
    W*)     level=warning ;;
    A*)     level=alert ;;
    E*)     level=emerg ;;
    *)      level=notice ;;
  esac
  if [[ "$#" -eq 1 ]]; then
    # Strip off unnecessary prefixes like "INFO:"
    string="${1#+([A-Z])?(:) }"
  else
    shift
    string="$@"
  fi
  logger -p "${facility}.${level}" -t "$(hostname -s)" "$string"
}

echo "Normal output"
logme INFO "Here we go..."
somecommand | logme
echo "Additional normal output"

(Lưu ý rằng chỉ ${var^^}có bash.)

Điều này tạo ra một hàm shell có thể sử dụng các syslogchức năng của hệ thống của bạn (với loggerlệnh ) to send things to system logs. Thelogme () `có thể được sử dụng với các tùy chọn tạo một dòng dữ liệu nhật ký hoặc với nhiều dòng đầu vào được xử lý trên stdin. Chơi với nó nếu nó được xử lý có vẻ hấp dẫn

Lưu ý rằng đây là một ví dụ và có lẽ không nên sao chép nguyên văn trừ khi bạn hiểu nó và biết rằng nó thực hiện chính xác những gì bạn cần. Một ý tưởng tốt hơn là lấy các khái niệm ở đây và tự thực hiện chúng trong các kịch bản của riêng bạn.


Cảm ơn rất nhiều cho câu trả lời chi tiết của bạn! Tôi thực sự đã sử dụng một function log () { cat >> $logfile }, về cơ bản là một phiên bản đơn giản hơn của bạn logme.
wchargein

Ngoài ra, người ta có thể quan tâm đến ts(1); cách sử dụng : { printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2.
wchargein

@wchargein - ts(1)không được cài đặt trên các hệ thống tôi sử dụng - FreeBSD, OSX và một hộp Ubuntu cũ. Bạn có thể cho chúng tôi biết những gì cung cấp nó?
ghoti

Đó là một phần của moreutils, bao gồm một số công cụ hay như sponge(1)(chỉ ghi vào tệp sau khi đóng stdin, vì vậy bạn có thể thực hiện something < foo | sponge foomà không bị chặn foobởi chuyển hướng) và vipe(1)(chèn trình soạn thảo văn bản vào đường ống).
wchargein

1

Cách thích hợp hơn để làm điều này là với { command; }hơn là (command). Lý do là khi các lệnh được nhóm với ()một lớp con được mở để thực thi các lệnh đó và do đó các biến được khởi tạo trong khối đó sẽ không có sẵn cho các phần khác của tập lệnh.

Thay vào đó khi chúng ta sử dụng {}để nhóm lệnh, các lệnh được thực thi trong cùng một shell và do đó các biến sẽ có sẵn cho các phần khác của tập lệnh.

echo "Some normal commands"

{
    var=1
    echo "Error: something happened"
    echo "Warning: this incident will be logged"
} >> logfile

echo "The value of var is: $var"
echo "More normal commands"

Ở đây, khi phần này được thực thi, $varbiến vẫn giữ nguyên giá trị của nó, trong trường hợp khác thì không.


Ngoài ra khi sử dụng trên một dòng, hãy nhớ có khoảng trắng ở giữa và chấm dứt lệnh bằng dấu chấm phẩy. Ví dụ : { command; }.
CMCDragonkai
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.