如何使用 bash 检查目录中的所有文件以查看哪些文件(如果有)被写入了内容?

如何使用 bash 检查目录中的所有文件以查看哪些文件(如果有)被写入了内容?

我运行了一个作用于多个“人”的脚本,并为每个人创建输出和错误文件。让我们这样说:

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 fL+0-size +0cNN

这样做的好处是不包含./前缀,即使用户名无法在区域设置中解码为文本,也可以工作,并且可以为您提供一个(默认情况下按词法)排序的列表。

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因此这些方法可能不适用于运行时间少于一厘秒的命令。删除Nanoseconds 并替换>>=-newerwith ! -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 *

相关内容