我有一个 shell 脚本,我试图向其传递参数date
,ARGV[1]
但该脚本给出了空白输出
这是命令:
#!/bin/bash
dt=$(date -d "yesterday" '+%m%d%Y')
cat /tmp/log.$AUTOSERVE.$dt \
| perl -ne '/STATUS:\s+(\w+).+MACHINE:\s+(\w+.\w+.\w+)$/ && print join( "\t", $1, $2 ). "\n"' \
| grep -E '(SUCCESS|FAILURE|TERMINATED)' \
| cut -f2 \
| sort \
| uniq -c \
| perl -ne '/^\s+(\d+)\s+(.*)$/ && print join("\t", '$ARGV[1]', $ENV{AUTOSERV}, $2, $1) . "\n"' $date_YYYYMMDD \
> /tmp/output.txt
我究竟做错了什么?
让我解释一下我想在这里做什么:
我们每天都会生成日志文件,其名称如下
log.$AUTOSERVE.mmddyyyy
日志文件包含如下数据:
更改了输入日期以便更好地理解:
Time Message
____________________________________________
[11/16/2023 07:13:45] CAUAJM_I_12345 The application has rollover
[11/16/2023 07:13:45] CAUAJM_I_11111 The machine 111.test.com has lost connection
[11/16/2023 07:13:45] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: FAILURE JOB: ABC MACHINE: 111.test.com EXITCODE: 1
[11/16/2023 07:13:45] CAUAJM_I_40245 [222.test.com connected to ABC]
[11/16/2023 07:13:45] CAUAJM_I_40245 EVENT: CHANGE_STATUS ALARM: JOBFAILURE JOB: ABC EXITCODE: 1
[11/16/2023 07:13:45] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: TERMINATED JOB: XYZ MACHINE: 222.test.com
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: STARTING JOB: 123 MACHINE: 333.test.com
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: SUCCESS JOB: 456 MACHINE: 444.test.com EXITCODE: 0
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: SUCCESS JOB: ABC123 MACHINE: 555.test.com
[11/16/2023 07:13:45] CAUAJM_I_40245 [222.test.com connected to ABC]
[11/16/2023 07:13:45] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: FAILURE JOB: ABC MACHINE: 111.test.com EXITCODE: 1
[11/16/2023 07:13:45] CAUAJM_I_40245 [333.test.com connected to 123]
[11/16/2023 07:13:45] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: TERMINATED JOB: XYZ MACHINE: 222.test.com
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: STARTING JOB: 123 MACHINE: 333.test.com
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: SUCCESS JOB: 456 MACHINE: 444.test.com
[11/16/2023 07:13:46] CAUAJM_I_40245 EVENT: CHANGE_STATUS STATUS: SUCCESS JOB: ABC123 MACHINE: 555.test.com EXITCODE: 0
此 shell 脚本过滤此log
文件中的 MACHINE 和 STATUS 搜索字符串,并计算每台计算机上运行的作业数量
我得到的输出是:
NP2 111.test.com 2
NP2 222.test.com 2
NP2 444.test.com 2
NP2 555.test.com 2
我尝试更改$date_YYYYMMDD
为$dt:
cat /tmp/log.$AUTOSERVE.dt \
| perl -ne '/STATUS:\s+(\w+).+MACHINE:\s+(\w+.\w+.\w+)$/ && print join( "\t", $1, $2 ). "\n"' \
| grep -E '(SUCCESS|FAILURE|TERMINATED)' \
| cut -f2 \
| sort \
| uniq -c \
| perl -ne '/^\s+(\d+)\s+(.*)$/ && print join("\t", $ARGV[1], $ENV{AUTOSERV}, $2, $1) . "\n"' $dt \
> /tmp/output.txt
但我收到以下错误:
Can't open 11152023: No such file or directory.
$AUTOSERVE
鉴于我有一个提供此输出中的值的环境变量NP2
,我期望的是:
11152023 NP2 111.test.com 2
11152023 NP2 222.test.com 2
11152023 NP2 444.test.com 2
11152023 NP2 555.test.com 2
答案1
听起来你想要类似的东西:
#! /bin/sh -
DT=$(date -d yesterday +%m%d%Y) || exit
export DT
exec perl -lne '
if (
($status, $machine) = /STATUS:\s+(\w+).+MACHINE:\s+(\w+\.\w+\.\w+)$/ and
$status =~ /^(SUCCESS|FAILURE|TERMINATED)\z/
) {$count{$machine}++}
END {
for (keys %count) {
print join "\t", $ENV{DT}, $ENV{AUTOSERVE}, $_, $count{$_};
}
}' < ~/tmp/log."$AUTOSERVE.$dt" > ~/tmp/output.txt
笔记:
不得在世界可写目录中使用具有固定名称的文件,例如
/tmp
(因此切换到此处,或在或/ / ...~/tmp
中使用专用区域)。/var
~/var
~/.local
$XDG_RUNTIME_DIR
该代码中没有任何特定于 bash 的内容,因此无需添加 bash 依赖项。
其中
-n
的额外参数perl
是脚本的输入正如克里斯已经说过的,你的引用有问题。
你有
AUTOSERV
/AUTOSERVE
差异。.
是匹配任何单个字符的正则表达式运算符。使用\.
或[.]
来匹配文字点。请注意, 的用法
date
是 GNU 特定的。并非所有date
实现都支持某个-d
选项,在那些支持的实现中,它可能用于完全不相关的东西,例如 BSD 上的东西,或者它们无法识别的东西yesterday
(例如date
busybox 或 toybox 的)。perl
如果您需要将该脚本移植到非 GNU 系统,还可以进行日期操作。您可以轻松地将其更改为使用单个正则表达式,例如:
/STATUS:\s+(?:SUCCESS|FAILURE|TERMINATED)\b.+MACHINE:\s+(\w+.\w+.\w+)$/
如果您希望机器列表按词法排序,请替换
keys %count
为。sort cmp, keys %count
exec
,在像这样的包装器脚本中很常见的就是保存一个进程。它告诉 shellperl
在同一进程中运行,而不是在子进程中运行并等待它。doescmd
+fork()
&exec(cmd)
,wait(child)
whileexec cmd
(可能应该被称为nofork cmd
)就是exec(cmd)
这样,即使输入时间更长,但对于系统来说运行起来更简单/更短,并且占用的资源更少。%m%d%Y
不是一个很好的时间戳格式选择。它是不明确的,并且它的词汇顺序(如 的输出中ls
)与时间顺序不匹配。%Y-%m-%d
或者%F
简称为更好,因为它被普遍认可,并且按词汇时间顺序排序(至少从 0001 到 9999 年)。cat
是连接文件的命令,对一个文件使用它没有什么意义。使用cmd < input > output
(或<input cmd >output
, 但是不是cmd > output < input
)还有一个好处是,如果input
无法打开阅读,则cmd
不会运行并且output
不会被破坏。
1 例如,在这里添加-MPOSIX
和 aBEGIN{@t = localtime; $t[3]--; $dt = strftime "%m%d%Y", @t}
或 甚至作为 hack 只是-M'POSIX;@t = localtime; $t[3]--; $dt = strftime "%m%d%Y", @t'
。
答案2
我突然想到的一个问题是第二perl
行:
perl -ne '/^\s+(\d+)\s+(.*)$/ && print join("\t", '$ARGV[1]', $ENV{AUTOSERV}, $2, $1) . "\n"' $date_YYYYMMDD
您正好可以及时使用单引号$ARGV[1]
,因此 shell 可以尝试解析它。通常$ARGV
不会设置shell 变量,因此传递给的结果行perl
是这样的:
perl -ne '/^\s+(\d+)\s+(.*)$/ && print join("\t", [1], $ENV{AUTOSERV}, $2, $1) . "\n"' $date_YYYYMMDD
这在语法上是有效的(但不太可能有用),因此您不会收到任何错误。
如果删除行中间的两个单引号,您几乎会得到看起来像您想要的东西。您需要换出-n
一个显式循环来读取标准输入以便您的@ARGV
价值能够被捕获。列表,包括@ARGV
,从零开始,所以我也改变了这一点。
perl -e 'while (<STDIN>) { chomp; /^\s+(\d+)\s+(.*)$/ && print join("\t", $ARGV[0], $ENV{AUTOSERV}, $2, $1) . "\n" }' "$date_YYYYMMDD"
这是一个可供您选择的管道,它将获取您的源文件并生成指定的输出:
awk -v date="$(date --date 'yesterday' +'%m%d%Y')" '
# Count instances of IP address for finished jobs
/SUCCESS|FAILURE|TERMINATED/ {
if (m = index($0, "MACHINE:")) {
# address is after "machine"
ip = substr($0, m+9, length($0))
if (s = index(ip, " ")) {
# discard trailing text too
ip = substr(ip, 1, s-1)
}
# capture address
seen[ip]++
}
}
# Output list of addresses and counts
END {
OFS="\t"
for (ip in seen) {
print date, ENVIRON["AUTOSERVE"], seen[ip], ip
}
}
' "/tmp/log.$AUTOSERVE.dt"
通过AUTOSERVE=NP2
合适的日期匹配,我从您的示例数据文件中得到了这个结果
11162023 NP2 2 222.test.com
11162023 NP2 2 111.test.com
11162023 NP2 2 555.test.com
11162023 NP2 2 444.test.com
可能值得注意的是,该构造if (m = index($0, "MACHINE:"))
是一个任务随后进行非零测试。如果我想要进行比较,我应该使用==
而不是=
.它可以等效地写成这样
m = index($0, "MACHINE:")
if (m<>0)
答案3
失败的原因首先是因为您正在退出单引号:
perl -ne '[...] '$ARGV[1]', [...]'
所以你的$ARGV[1]
被 shell 看到,而不是perl
。接下来,您实际上没有ARGV
这里的数组,因为您告诉perl
从标准输入读取,因为您正在使用-n
:
$ perl -le 'print "$ARGV[0]"' foo
foo
$ perl -nle 'print "$ARGV[0]"' foo
$
你可以任何一个use-n
这意味着您可以通过管道传输数据或要求 perl 自动加载并迭代文件,或者您可以传递参数,但不能同时传递两者。
所以,你真正想做的是:
export dt=$(date -d "yesterday" '+%m%d%Y')
perl -ne '/STATUS:\s+(\w+).+MACHINE:\s+(\w+.\w+.\w+)$/ && print join( "\t", $1, $2 ). "\n"' /tmp/log.$AUTOSERVE.dt |
grep -E '(SUCCESS|FAILURE|TERMINATED)' |
cut -f2 |
sort |
uniq -c |
perl -ne '/^\s+(\d+)\s+(.*)$/ && print join("\t", $ENV{dt}, $ENV{AUTOSERV}, $2, $1) . "\n"' > /tmp/output.txt
或者,由于您已经在使用perl
,并假设我正确猜测您的数据:
export dt=$(date -d "yesterday" '+%m%d%Y')
perl -lne '/STATUS:\s+(SUCCESS|FAILURE|TERMINATED).+MACHINE:\s+(\w+.\w+.\w+)$/ &&
print "$2"' /tmp/log.$AUTOSERVE.dt |
sort |
uniq -c |
perl -ne '/^\s+(\d+)\s+(.*)$/ &&
print join("\t", $ENV{dt}, $ENV{AUTOSERV}, $2, $1) . "\n"' > /tmp/output.txt
甚至:
export dt=$(date -d "yesterday" '+%m%d%Y')
perl -lne '
if(/STATUS:\s+(SUCCESS|FAILURE|TERMINATED).+MACHINE:\s+(\w+.\w+.\w+)$/){
$k{$2}++;
}
END{
foreach $key (keys(%k)){
print "$ENV{dt}\t$ENV{AUTOSERV}\t$key\t$k{$key}"
}
}' > /tmp/output.txt