我运行了一个作用于多个“人”的脚本,并为每个人创建输出和错误文件。让我们这样说:
output_alice.txt
error_alice.txt
output_bob.txt
error_bob.txt
.
.
.
我想要一个命令来扫描所有错误文件 ( error_<name>.txt
) 并回显已写入内容的文件(而不是空),作为识别脚本因错误而退出的“人”的快速方法。是否有捷径可寻?我知道如何使用 grep 对字符串执行此操作,例如grep -r <substring> .
,但不知道如何检查是否有任何内容。
答案1
请注意,bash 不是终端,它是众多之一贝壳,它们是某些专门运行命令的编程语言的解释器。与大多数应用程序一样,它可以将其输入/输出连接到终端设备或任何其他类型的文件。
要以 bash 和大多数其他 Unix shell 语言列出当前工作目录中l
命名的至少包含一行的文件,您可以执行以下操作:error_anything.txt
grep -l '^' error_*.txt
其中^
是与主题开头匹配的正则表达式,主题是文件中的每一行grep
。
对于至少有一个非空文本行的人:
grep -l . error_*.txt
哪里.
匹配任何单个字符。请注意,对于使用不同于区域设置的字符映射编码的文件,如果其内容无法解码为文本,则可能无法匹配非空行。
另请注意,并非所有grep
实现都会报告仅包含一个未终止行的文件(其中一个缺少行分隔符,如 的输出中所示printf invalid-text-as-missing-the-last-newline
)。
另一种方法是查找至少包含一个字节的文件:
find -L . ! -name . -prune -name 'error_*.txt' -type f -size +0c
这还有一个好处是可以忽略不属于该类型的文件常规的(例如目录、套接字...)
或者使用 zsh shell:
print -rC1 -- error_*.txt(N-.L+0)
对于符号链接,考虑其目标的大小和类型,其行为相当于和(对于ullglob 来说-
,如果没有匹配的文件,则不会报告错误)。-L
.
-type f
L+0
-size +0c
N
N
这样做的好处是不包含./
前缀,即使用户名无法在区域设置中解码为文本,也可以工作,并且可以为您提供一个(默认情况下按词法)排序的列表。
r
您可以将其扩展为仅打印用户名(第一个之后的文件 oot 名称的部分_
):
{}{ print -rC1 -- ${@#*_}; } error_*.txt(N-.L+0:r)
要列出error
自运行命令以来已修改的文件,您可以使用-newer
谓词 of并与在运行命令之前find
已编辑的文件进行比较:touch
touch .before
my-command-that-may-write-to-error-files
find -L . ! -name . -prune -name 'error_*.txt' -type f -size +0c -newer .before
在 zsh 中,您可以将find
命令替换为:
print -rC1 -- error_*.txt(N-.L+0e['[[ $REPLY -nt .before ]]'])
对于某些find
实现,您可以替换! -name . -prune
为-mindepth 1 -maxdepth 1
,但-maxdepth 1
也可以在这里工作,因为深度 0 ( .
) 的文件与其他条件不匹配(它既不匹配-name 'error_*.txt'
也不匹配-type f
)。
date
通过and的 GNU 实现find
(这也是find
引入-maxdepth
谓词的实现),您可以通过执行以下操作来避免创建该.before
文件:
before=$(date +'@%s.%N')
my-command-that-may-write-to-error-files
find -L . -maxdepth 1 -name 'error_*.txt' -type f -size +0c -newermt "$before"
使用 时zsh
,您可以将 替换before=$(date +'@%s.%N')
为print -Pv before '@%D{%s.%N}'
或before=${(%):-@%{%s.%N}D}
或before=@$EPOCHREALTIME
(在 之后zmodload zsh/datetime
);您可以再次避免find
调用全局限定符,甚至再次使用匿名函数来临时变量,但这变得非常复杂:
zmodload zsh/stat
zmodload zsh/datetime
() {
my-command-that-may-write-to-error-files
print -rC1 error_*.txt(N-.L+0e['
stat -F %s.%N -A2 +mtime -- $REPLY && (( $2 > $1 )) '])
} $EPOCHREALTIME
请注意,至少在 Linux 上,尽管系统和文件系统支持纳秒精度,但粒度要小得多。您甚至可以发现修改时间是在修改某个值时设置的,该值早于初始调用date
或引用,$EPOCHREALTIME
因此这些方法可能不适用于运行时间少于一厘秒的命令。删除N
anoseconds 并替换>
为>=
或-newer
with ! -older
(如果您的find
实现支持它,但不太可能)可能是更好的方法。
答案2
GNUfind
提供了非 POSIX 选项来列出空文件,只需否定该测试:
find /path/to/dir -type f -name 'error_*.txt' ! -empty
为了不是-maxdepth 1
在路径后添加子目录中搜索。
在 POSIX 中find
检查文件大小是0
可行的:
find /path/to/dir -type f -name 'error_*.txt' ! -size 0
答案3
只需 grep for .
,这意味着任何字符。空文件没有字符,因此搜索.
将显示非空文件。例如:
$ touch empty1 empty2 empty3
$ echo "not empty!" > non_empty
$ ls -l
total 4
-rw-r--r-- 1 terdon terdon 0 Aug 11 13:13 empty1
-rw-r--r-- 1 terdon terdon 0 Aug 11 13:13 empty2
-rw-r--r-- 1 terdon terdon 0 Aug 11 13:13 empty3
-rw-r--r-- 1 terdon terdon 11 Aug 11 13:13 non_empty
现在,我们 grep:
$ grep -- . ./*
non_empty:not empty!
并且,仅获取名称:
$ grep -l -- . ./*
non_empty
请注意,grep .
不会找到没有空行(一个或多个字符)的文件\n
。为此,您应该grep '^'
按照建议使用史蒂芬的回答。
答案4
仅限 GNU sed。就像命令的替代方案一样grep
:
sed -sn 1F error_*.txt
!我没有遇到F
手册页中的命令,但它有效。特别是,我在非空文件的第一行插入文件名sed -i 1F *