bash xargs 和 find, rm 抱怨文件名中的 \n(换行符)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8519219/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
xargs and find, rm complaining about \n (newline) in filename
提问by user1076412
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,但我迷路了。
回答by Jens
The lsappends a newline and the last xargs -0 says the newline is part of the file name.
Run the last xargs with -d '\n'instead of -0.
该ls追加一个换行符,最后xargs的-0表示,新行是文件名的一部分。使用-d '\n'而不是运行最后一个 xargs -0。
BTW, due to the way xargs works, your whole pipe is a bug waiting to happen. Consider a really long file name list produced by the find, so that the xargs -0 lsruns lsmultiple times with subsets of the filenames. Only the oldest of the last lsinvocationwill make it past the tail -1. If the oldest file is actually, say, the very first filename output by find, you are deleting a younger file.
顺便说一句,由于 xargs 的工作方式,您的整个管道是一个等待发生的错误。考虑由 生成的非常长的文件名列表find,以便使用文件名的子集多次xargs -0 ls运行ls。只有最后一次ls调用中最旧的才能使其通过tail -1. 如果最旧的文件实际上是由 输出的第一个文件名find,则您正在删除一个较新的文件。
回答by sorpigal
Any solution involving lsis absolutely wrong.
任何涉及的解决方案ls都是绝对错误的。
The correct way to do this is to use findto fetch the set of files, sortto order them chronologically, filter out all but the first, then rmto delete. @Ken had this mostly right, missing only a few details.
执行此操作的正确方法是使用find获取文件集,sort按时间顺序排列它们,过滤除第一个之外的所有文件,然后rm删除。@Ken 大体上是对的,只遗漏了一些细节。
find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %pfind /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\n' |\
sort -n |\
head -n 1 |\
cut -d' ' -f2- |\
xargs echo rm
' |\
sort -z -n | \
{ IFS= read -d '' file ; [ -n "$file" ] && echo rm -f "$(cut -d' ' -f2- <<<"$file")" ; }
Remove the echoabove 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 -printf "%T@ %p\n" | sort -n | head -1 | cut -d" " -f2- | xargs ls -al
The difference is that we can rely on headand can use cuton a pipe instead of doing anything crazy.
不同之处在于我们可以依赖head并且可以cut在管道上使用,而不是做任何疯狂的事情。
回答by Ken
You can also use find to print out the modification time, sort, cut and xargs at will:
也可以使用find随意打印出修改时间、sort、cut和xargs:
find /home/backups \
-type f -iregex '.*\.t?gz$' \
-mtime +60 -exec rm {} \;
回答by thiton
lsemits newlines as separators, so you need to replace the second xargs -0with xargs -d '\n'. Breaks, though, if the oldest file has a newline in its name.
ls发出换行符作为分隔符,所以你需要更换第二xargs -0带xargs -d '\n'。但是,如果最旧的文件在其名称中有换行符,则会中断。
回答by sehe
EditI missed the point of ls -tthere.
编辑我错过了ls -t那里的重点。
Might I suggest doing it much simpler, e.g.
我是否建议这样做更简单,例如
#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, 'find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | ./mytail | xargs -0 rm
'))
{
previous = element;
// if you have c++0x support, use this _instead_ for performance:
previous = std::move(element);
}
std::cout << previous << '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
' << std::flush;
}
which will delete any matching file older than a specific age (60 days, in the example)
这将删除任何早于特定年龄(在示例中为 60 天)的匹配文件
You used tailbut haven't told it to look for null-delimiters.
您使用过tail但没有告诉它查找空分隔符。
Regardless, here is a util that you could use to return the last 0-delimited element:
无论如何,这是一个可用于返回最后一个以 0 分隔的元素的实用程序:
##代码##Use it as
将其用作
##代码##回答by fge
ls -tr $(find /home/backups -name '*.gz' -o -name '*.tgz')|head -1|xargs rm -f
ls -tr $(find /home/backups -name '*.gz' -o -name '*.tgz')|head -1|xargs rm -f

