简体   繁体   English

xargs和find,rm在文件名中抱怨\ n(换行符)

[英]xargs and find, rm complaining about \n (newline) in filename

I am trying to delete the oldest file in a tree with a script in Debian. 我试图用Debian中的脚本删除树中最旧的文件。

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | xargs -0 ls -t | tail -1 | xargs -0 rm

But I am getting an error: 但我收到一个错误:

rm: cannot remove `/home/backups/tree/structure/file.2011-12-08_03-01-01.sql.gz\n': No such file or directory

Any ideas what I am doing wrong (or is there an easier/better way?), I have tried to RTFM, but am lost. 任何想法我做错了(或者有更简单/更好的方法?),我尝试过RTFM,但我迷失了。

The ls appends a newline and the last xargs -0 says the newline is part of the file name. ls附加一个换行符,最后一个xargs -0表示换行符是文件名的一部分。 Run the last xargs with -d '\\n' instead of -0 . 使用-d '\\n'而不是-0运行最后一个xargs。

BTW, due to the way xargs works, your whole pipe is a bug waiting to happen. 顺便说一下,由于xargs的工作方式,你的整个管道都是等待发生的错误。 Consider a really long file name list produced by the find , so that the xargs -0 ls runs ls multiple times with subsets of the filenames. 考虑一个由find生成的非常长的文件名列表,以便xargs -0 ls使用文件名的子集多次运行ls Only the oldest of the last ls invocation will make it past the tail -1 . 只有最后一次ls调用中最旧的将使它超过tail -1 If the oldest file is actually, say, the very first filename output by find , you are deleting a younger file. 如果最旧的文件实际上是,例如,通过find输出的第一个文件名,则删除较年轻的文件。

Any solution involving ls is absolutely wrong. 任何涉及ls解决方案都是绝对错误的

The correct way to do this is to use find to fetch the set of files, sort to order them chronologically, filter out all but the first, then rm to delete. 执行此操作的正确方法是使用find来获取文件集, sort按时间顺序sort ,过滤掉除第一个之外的所有文件,然后删除rm @Ken had this mostly right, missing only a few details. @Ken这个大概是正确的,只缺少一些细节。

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\0' |\
    sort -z -n | \
    { IFS= read -d '' file ; [ -n "$file" ] && echo rm -f "$(cut -d' ' -f2- <<<"$file")" ; }

Remove the echo above to actually perform the deletion. 删除上面的echo以实际执行删除。

The above code works even for files which have spaces, newlines or other unusual values in the file names. 上述代码甚至适用于文件名中包含空格,换行符或其他异常值的文件。 It will also do nothing harmful when there are no results. 没有结果时,它也不会造成任何伤害。

If you don't care about breaking on newlines in filenames this gets a bit easier 如果你不关心打破文件名中的换行符,这会更容易一些

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\n' |\
    sort -n |\
    head -n 1 |\
    cut -d' ' -f2- |\
    xargs echo rm

The difference is that we can rely on head and can use cut on a pipe instead of doing anything crazy. 不同之处在于我们可以依靠head并且可以在管道上使用cut而不是做任何疯狂的事情。

您还可以使用find随意打印修改时间,排序,剪切和xargs:

find /home/backups -printf "%T@ %p\n" | sort -n | head -1 | cut -d" " -f2- | xargs ls -al

ls emits newlines as separators, so you need to replace the second xargs -0 with xargs -d '\\n' . ls作为分隔符发出换行符,因此您需要将第二个xargs -0替换为xargs -d '\\n' Breaks, though, if the oldest file has a newline in its name. 但是,如果最旧的文件名称中有换行符,则会中断。

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | xargs -0 stat --format '%010Y:%n' | sort -n | head -n 1 | cut -d: -f2- | xargs -d '\n' rm 

from: 在Linux中按日期排序文件列表(包括子目录)

Edit I missed the point of ls -t there. 编辑我错过了ls -t那里的点。

Might I suggest doing it much simpler, eg 我可能会建议这样做更简单,例如

find /home/backups \
    -type f -iregex '.*\.t?gz$' \
    -mtime +60 -exec rm {} \;

which will delete any matching file older than a specific age (60 days, in the example) 这将删除任何早于特定年龄的匹配文件(在示例中为60天)


You used tail but haven't told it to look for null-delimiters. 你使用了 tail但没有告诉它寻找null-delimiters。

Regardless, here is a util that you could use to return the last 0-delimited element: 无论如何,这是一个可以用来返回最后一个0分隔元素的util:

#include <string>
#include <iostream>
#include <cstdio>

int main(int argc, const char *argv[])
{
    std::cin.unsetf(std::ios::skipws);
    if (!  (freopen(NULL, "wb", stdout) && freopen(NULL, "rb", stdin) ))
    {
        perror("Cannot open stdout/in in binary mode");
        return 255;
    }

    std::string previous, element;
    while (std::getline(std::cin, element, '\0'))
    {
        previous = element; 
        // if you have c++0x support, use this _instead_ for performance:
        previous = std::move(element);
    }

    std::cout << previous << '\0' << std::flush;
}

Use it as 用它作为

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | ./mytail | xargs -0 rm 

ls -tr $(find /home/backups -name '*.gz' -o -name '*.tgz')|head -1|xargs rm -f

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM