使用 shell 工具 awk 编辑 fslint 的输出 |查询 | sed

使用 shell 工具 awk 编辑 fslint 的输出 |查询 | sed

任务是根据一组规则,将此文本文件(实用程序的输出)转换fslint为 bash 脚本,其中包含要删除的重复文件的命令行和要保留的文件的注释行。rm

规则基本上是说:仅删除特定目录中的重复文件。

目标是清理多年来在多个操作系统(Mac OS X、Windows、Linux)上积累的约 1 TB 重复项。所有数据均已复制到 Linux 驱动器。

#3 x 697,612,024        (1,395,236,864) bytes wasted
/path/to/backup-100425/file_a.dat
/another/path/to/backup-disk-name/171023/file_a.dat
/yet/another/path/to/labs data/some/path/file_a.dat
#4 x 97,874,344 (293,634,048)   bytes wasted
/path/to/backup-100425/file b.mov
/another/path/to/backup-140102/file b.mov
/backup-120708/Library/some/path/file b.mov
/some/other/path/to/backup-current/file b.mov
#2 x 198,315,112        (198,316,032)   bytes wasted
/path/to/backup-100425/file_c.out
/another/path/to/backup-disk-name/171023/file_c.out

第一行表示有 3 个相同的副本file_a.dat,接下来的 3 行列出了它们的路径。理想情况下,应在此处删除 2 个副本。我所说的是带有 6 位数字的目录(YYMMDD 格式的日期)历史备份目录

规则,适用按这个顺序每组相同的文件是:

  1. 如果文件位于包含目录的路径中Library,则保留它。
  2. 如果文件位于labs data或中backup-current,则保留该文件,并删除其中的所有重复项历史备份目录
  3. 如果文件位于历史备份目录中,则将该文件保留在最新的备份目录中,并删除旧的重复项。
  4. 否则保留文件。

这是所需的输出:

#!/bin/bash
#3 x 697,612,024        (1,395,236,864) bytes wasted
rm '/path/to/backup-100425/file_a.dat'
rm '/another/path/to/backup-disk-name/171023/file_a.dat'
#/yet/another/path/to/labs data/some/path/file_a.dat
#4 x 97,874,344 (293,634,048)   bytes wasted
rm '/path/to/backup-100425/file b.mov'
rm '/another/path/to/backup-140102/file b.mov'
#/backup-120708/Library/some/path/file b.mov
#/some/other/path/to/backup-current/file b.mov
#2 x 198,315,112        (198,316,032)   bytes wasted
rm '/path/to/backup-100425/file_c.out'
#/another/path/to/backup-disk-name/171023/file_c.out

我对 shell 工具 awk、grep 和 sed 不是很熟悉,阅读后这个线程我意识到我的初稿在概念上是错误的,“这是对 [我] 在像 C 这样的命令式语言中所做的事情的幼稚翻译”。

事实上,我们在这里处理的不是文件,但与一个文件的内容

对于这种情况使用 shell 脚本是否合适?
如果是,高效的脚本会是什么样子?

編輯:在阅读了 @Ed 的答案和代码后,我试图澄清任务和要求,这完美地解决了问题。

答案1

鉴于我愿意投入多少时间,我不明白您的要求列表,但这里有一个脚本,用于对您似乎感兴趣的文件类型进行分类和打印,希望您能弄清楚其余的内容:

$ cat tst.awk
/^#/ { prt(); print; next }
{ files[$0] }
END { prt() }

function prt(   file, isLibrary, isLabsBack, isNothing) {
    for (file in files) {
        if ( file ~ /(^|\/)Library(\/|$)/ ) {
            isLibrary[file]
        }
        else if ( file ~ /(^|\/)(labs data|backup-current)(\/|$)/ ) {
            isLabsBack[file]
        }
        else {
            isNothing[file]
        }
    }
    for (file in isLibrary) {
        print "Library", file
    }
    for (file in isLabsBack) {
        print "LabsBack", file
    }
    for (file in isNothing) {
        print "Nothing", file
    }
    delete files
}

$ awk -f tst.awk file
#3 x 697,612,024        (1,395,236,864) bytes wasted
LabsBack /yet/another/path/to/labs data/some/path/file_a.dat
Nothing /another/path/to/backup-disk-name/171023/file_a.dat
Nothing /path/to/backup-100425/file_a.dat
#4 x 97,874,344 (293,634,048)   bytes wasted
Library /backup-120708/Library/some/path/file b.mov
LabsBack /some/other/path/to/backup-current/file b.mov
Nothing /path/to/backup-100425/file b.mov
Nothing /another/path/to/backup-140102/file b.mov
#2 x 198,315,112        (198,316,032)   bytes wasted
Nothing /path/to/backup-100425/file_c.out
Nothing /another/path/to/backup-disk-name/171023/file_c.out

答案2

这是为感兴趣的人提供问题中提到的所需输出的代码。这只是 @Ed 真正智能代码的一个微小改编。

BEGIN { print "#!/bin/bash" }
/^#/ { prt(); print; next }
{ files[$0] }
END { prt() }

function prt(   file, isDate, isKeep, isDelete, backup, latest, pats) {
    # file exists in a current backup directory (yes|no)
    backup = "no"
    # latest historical backup date
    latest = "000000"
    for (file in files) {
        if ( file ~ /\/Library\// ) {
            # files to check manually
            isKeep[file]
        }
        else if ( file ~ /\/(labs data|backup-current)\// ) {
            # backup files to keep
            isKeep[file]
            backup = "yes"
        }
        else if ( match(file, /\/(backup-disk-name\/|backup-)([0-2][0-9][0-1][0-9][0-3][0-9])\//, pats) != 0 ) {
            # files in historical backup directories
            if ( pats[2] > latest ) {
                latest = pats[2]
            }
            isDate[file] = pats[2]
        }
        else {
            # unclassified files to check manually
            isKeep[file]
        }
    }
    for (file in isDate) {
        if ( isDate[file] == latest && backup == "no") {
            isKeep[file]
        }
        else {
            isDelete[file]
        }
    }
    for (file in isKeep) {
        print "#", file
    }
    for (file in isDelete) {
        # use single quotes to escape special characters in file
        # use gensub() to escape single quotes in file
        print "rm", "'" gensub(/'/,"'\\\\''", "g", file) "'"
    }
    delete files
}

最后我想分享一些想法。我希望我没有离题太多。
几周前,我决定最终清理那些巨大的备份数据(有些文件有超过 10 个重复项)。但我找不到自动化该任务的工具。我不想为此启动 C 程序,也不想采用 Perl 方式。所以我知道我必须(而且我想)走外壳之路。但我不知道从哪里开始,只停留在第一行。

读了很多之后,我还是很困惑。所以我决定在SE上发布我的问题。
当我第一次阅读@Ed 的代码时,我想“到底是什么!”。然后,当我拿到它时,我意识到这是一段精彩的代码,高效且清晰。

所以我们到了。大约一周前,我awk对RegExp一无所知,也知之甚少。现在,感谢 @Ed 的贡献,我已经能够编写“我的”第一个awk脚本,更好地理解 RegExp 世界,并完成手头的任务。更重要的是,我现在有足够的信心自己深入研究 RegExpawk和其他文本处理 shell 工具。这也激励我为SE做出更多贡献。
我只是想分享我的个人经历,给那些像我一样陷入困境的人带来希望,比如面对一座山。

相关内容