Pipa / redirect sekelompok perintah

8

Saat ini saya menggunakan pengaturan berikut untuk mengarahkan output dari beberapa perintah:

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

Ini sangat berguna, dan itu juga berfungsi dengan pipa.

Apakah ini cara terbaik untuk melakukan ini? Apakah ada alternatif yang harus saya pertimbangkan?

wchargin
sumber
Begitulah cara saya melakukannya. Apakah Anda mengalami masalah tertentu dengan pendekatan ini?
Bratchley
@ JoelDavis Tidak, hanya ingin tahu apakah ada cara yang lebih baik untuk melakukannya. Dari jawaban yang saya terima, sepertinya ada! :)
wchargin

Jawaban:

15

Alternatifnya adalah menggunakan kawat gigi, bukan tanda kurung. Perubahan ini mengeksekusi perintah di shell saat ini , bukan di subkulit

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

Ini sangat relevan ketika Anda memodifikasi variabel di dalam grup:

$ 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
glenn jackman
sumber
Luar biasa — terima kasih! Ini bekerja lebih baik dengan lekukan saya juga. (Vim indentasi { }tapi tidak ( ).)
wchargin
2
Perhatikan bahwa Anda dapat memecah grup perintah menjadi satu baris dengan cara apa pun; misalnya, (echo msg1; echo msg2)- tetapi, dengan kawat gigi, harus { echo msg1; echo msg2;}, dengan spasi setelah {dan titik koma ( ;) atau ampersand ( &) sebelum }.
G-Man Mengatakan 'Reinstate Monica'
4

Jawaban Glenn adalah jawaban yang bagus - perbedaan antara ( ... )dan { ... }penting.

Salah satu strategi yang sering saya gunakan untuk output kesalahan seperti apa yang ada di pertanyaan Anda adalah teeperintah. Anda dapat melakukan sesuatu seperti ini:

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"

The teeperintah akan mengirim output ke dua tempat; -aopsi "menambahkan" output ke file bernama, dan perintah juga akan meneruskan input ke stdout. Pada >&2akhir baris mengarahkan pengalihan teeke stderr, yang dapat ditangani secara berbeda (yaitu dalam pekerjaan cron).

Satu tip lain yang sering saya gunakan dalam skrip shell adalah untuk mengubah perilaku debug atau output verbose berdasarkan apakah skrip berjalan pada terminal atau memiliki -vopsi yang disediakan. Sebagai contoh:

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

Skrip dapat dimulai dengan sesuatu yang generik seperti ini di bagian atas, dengan output Verbose dan Debug tercecer di seluruh skrip. Ini hanya satu cara untuk melakukannya - ada banyak, dan orang yang berbeda akan memiliki cara mereka sendiri untuk menangani hal ini, terutama jika mereka sudah ada beberapa saat. :)

Satu lagi opsi adalah menangani output Anda dengan "handler" - fungsi shell yang dapat melakukan lebih banyak hal cerdas. Sebagai contoh:

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

(Perhatikan bahwa ${var^^}hanya bash.)

Ini menciptakan fungsi shell yang dapat menggunakan syslogfungsi sistem Anda (dengan loggerperintah ) to send things to system logs. Thelogme () `fungsi dapat digunakan baik dengan opsi yang menghasilkan satu baris data log, atau dengan beberapa baris input yang diproses pada stdin. Main dengannya jika tampaknya menarik.

Perhatikan bahwa ini adalah contoh dan mungkin tidak boleh disalin kata demi kata kecuali Anda memahaminya dan tahu bahwa itu tepat apa yang Anda butuhkan. Ide yang lebih baik adalah mengambil konsep di sini dan mengimplementasikannya sendiri dalam skrip Anda sendiri.

ghoti
sumber
Terima kasih banyak atas jawaban terinci Anda! Saya sebenarnya sudah menggunakan function log () { cat >> $logfile }, yang pada dasarnya adalah versi sederhana dari Anda logme.
wchargin
Juga, orang mungkin tertarik pada ts(1); penggunaan: { printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2.
wchargin
@wchargin - ts(1)tidak diinstal pada sistem yang saya gunakan - FreeBSD, OSX, dan kotak Ubuntu lama. Bisakah Anda memberi tahu kami apa yang menyediakannya?
ghoti
Itu bagian dari moreutils, yang juga mencakup beberapa alat yang bagus seperti sponge(1)(menulis ke file hanya setelah stdin ditutup, sehingga Anda dapat melakukannya something < foo | sponge footanpa mengganggu foodengan pengalihan), dan vipe(1)(masukkan editor teks ke dalam pipa).
wchargin
1

Cara yang lebih tepat untuk melakukan ini adalah dengan { command; }daripada (command). Alasannya adalah bahwa ketika perintah dikelompokkan dengan ()subkulit dibuka untuk mengeksekusi perintah itu dan dengan demikian variabel yang diinisialisasi selama blok itu tidak akan tersedia untuk bagian lain dari skrip.

Sebaliknya ketika kita gunakan {}untuk pengelompokan perintah, perintah dieksekusi dalam shell yang sama dan dengan demikian variabel akan tersedia untuk bagian lain dari skrip.

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"

Di sini, ketika bagian ini dieksekusi $varvariabel mempertahankan nilainya, sedangkan dalam kasus lain tidak akan.

Kannan Mohan
sumber
Juga ketika menggunakan satu baris, ingatlah untuk memiliki spasi di antaranya, dan akhiri perintah dengan titik koma. Contoh: { command; }.
CMCDragonkai