我当前使用以下设置来重定向多个命令的输出:
echo "Some normal commands"
(
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"
这非常有用,并且也适用于管道。
这是最好的方法吗?我应该考虑其他选择吗?
答案1
另一种方法是使用大括号代替圆括号。此更改执行中的命令当前的外壳,不在子外壳
echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"
参考: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
答案2
格伦的回答很好——( ... )
和之间的区别{ ... }
很重要。
我经常用于错误输出(例如您问题中的内容)的一种策略是命令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"
该tee
命令将输出发送到两个地方;-a
选项“附加”输出到指定文件,并且该命令还将输入传递到标准输出。>&2
该行末尾的 会将 的 stdout 重定向到tee
stderr,这可能会以不同的方式处理(即在 cron 作业中)。
我经常在 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
脚本可以从顶部这样的通用内容开始,详细和调试输出散布在整个脚本中。这只是一种方法——有很多方法,不同的人都有自己的方法来处理这些事情,特别是如果他们已经存在了一段时间。 :)
另一种选择是使用“处理程序”处理输出——一种可以执行更智能操作的 shell 函数。例如:
#!/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"
(请注意,${var^^}
仅限 bash。)
这将创建一个 shell 函数,该函数可能会使用您系统的syslog
函数(使用logger
命令) to send things to system logs. The
logme()` 函数可以与生成单行日志数据的选项一起使用,也可以与在 stdin 上处理的多行输入一起使用。如果可以的话,请使用它看起来很吸引人。
请注意,这是一个例子并且可能不应该逐字复制,除非您理解它并且知道它正是您所需要的。更好的想法是采用此处的概念并在您自己的脚本中自行实现它们。
答案3
更合适的方法是使用{ command; }
而不是(command)
。原因是,当命令与()
子 shell 分组时,打开子 shell 来执行这些命令,因此在该块期间初始化的变量将不可用于脚本的其他部分。
相反,当我们用于{}
命令分组时,命令在同一 shell 中执行,因此变量将可用于脚本的其他部分。
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"
在这里,当执行此部分时,$var
变量保留其值,而在其他情况下则不会。