管道/重定向一组命令

wch*_*gin 10 bash io-redirection shell-script

我目前使用以下设置来重定向多个命令的输出:

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

这非常有用,它也适用于管道。

这是最好的方法吗?有没有我应该考虑的替代方案?

gle*_*man 17

另一种方法是使用大括号而不是圆括号。此更改在当前shell 中执行命令,而不是在子 shell 中

echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"
Run Code Online (Sandbox Code Playgroud)

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

当您修改组内的变量时,这尤其重要:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,无论哪种方式,您都可以将命令组折叠成一行;例如,`(echo msg1; echo msg2)`——但是,使用大括号,它必须是`{echo msg1; echo msg2;}`,在 `{` 之后有一个空格,在 `}` 之前有一个分号 (`;`) 或一个与号 (`&`)。 (2认同)

gho*_*oti 5

格伦的回答很好——( ... ){ ... }是很重要的。

我经常用于错误输出的一种策略是tee命令。你可以这样做:

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"
Run Code Online (Sandbox Code Playgroud)

tee命令会将输出发送到两个地方;-a选项“附加”输出到命名文件,并且该命令还将输入传递到标准输出。的>&2在该行重定向的端部tee的标准输出到stderr,其可以被不同地处理(即,在定时任务)。

我经常在 shell 脚本中使用的另一个技巧是根据脚本是在终端上运行还是提供了-v选项来更改调试或详细输出的行为。例如:

#!/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
Run Code Online (Sandbox Code Playgroud)

脚本可以从顶部这样的通用内容开始,详细和调试输出散布在整个脚本中。这只是一种方法——有很多方法,不同的人都有自己的方法来处理这些事情,特别是如果他们已经存在一段时间了。:)

另一种选择是使用“处理程序”来处理您的输出——一种可以做更智能事情的外壳函数。例如:

#!/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"
Run Code Online (Sandbox Code Playgroud)

(请注意,这${var^^}是仅限 bash 的。)

这将创建一个 shell 函数,它可以使用您系统的syslog函数(使用logger命令) to send things to system logs. Thelogme()` 函数可以与生成单行日志数据的选项一起使用,也可以与在 stdin 上处理的多行输入一起使用。如果它可以使用它似乎很有吸引力。

请注意,这是一个示例,除非您理解它并知道它完全符合您的需要,否则可能不应逐字复制。更好的主意是采用此​​处的概念并在您自己的脚本中自己实现它们。