Answers:
解析的输出ls
是不可靠的。
而是使用find
来定位文件并按sort
时间戳对其进行排序。例如:
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
# do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
首先,这些find
命令将所有文件和目录定位在当前目录(.
)中,而不是当前目录(-maxdepth 1
)的子目录中,然后输出:
时间戳很重要。该%T@
格式说明用于-printf
分解成T
,这表示文件(修改时间)和“最后修改时间” @
,其指示“从1970年开始秒”,包括分数秒。
该空间仅仅是一个任意的定界符。文件的完整路径是我们以后可以引用的路径,而NULL字符是终止符,因为它是文件名中的非法字符,因此可以确保我们知道到达该路径的末尾。文件。
我已包括在内,2>/dev/null
以便排除用户无权访问的文件,但排除有关被排除的错误消息。
该find
命令的结果是当前目录中所有目录的列表。该列表通过管道传递sort
到:
-z
将NULL视为行终止符而不是换行符。-n
数字排序由于1970年以来的秒数总是增加,因此我们希望时间戳为最小的文件。的第一个结果sort
将是包含最小编号时间戳的行。剩下的就是提取文件名。
的结果find
,sort
管道是通过传递进程替换到while
它,仿佛它是标准输入一个文件被读取。while
依次调用read
以处理输入。
在上下文中,read
我们将该IFS
变量设置为nothing,这意味着空格不会被不恰当地解释为定界符。read
被告知-r
,禁用逃生扩张,以及-d $'\0'
,这使得最终的行分隔符NULL,匹配我们的输出find
,sort
管道。
代表最早的文件路径的第一个数据块(其时间戳和空格之后)被读入变量line
。接下来,将参数替换与表达式一起使用,该表达式#*
简单地替换了从字符串开头到第一个空格(包括空格)在内的所有字符。这将删除修改时间戳,仅保留文件的完整路径。
此时,文件名已存储在其中$file
,您可以使用它进行任何操作。完成使用$file
该while
语句的操作时,该语句将循环并read
再次执行该命令,提取下一个块和下一个文件名。
不,更简单的方法是越野车。
如果您使用ls -t
或管道传输到head
或tail
(或其他任何内容),则会在文件名中使用换行符的文件上断开。如果mv $(anything)
这样,名称中带有空格的文件将导致损坏。如果mv "$(anything)"
这样,文件名中带有尾随换行符的文件将导致损坏。如果您read
不这样做,-d $'\0'
则将破坏名称中带有空格的文件。
也许在特定情况下,您肯定会知道一种简单的方法就足够了,但是如果您避免这样做,则绝对不要在脚本中编写这样的假设。
#!/usr/bin/env bash
# move to the first argument
dest="$1"
# move from the second argument or .
source="${2-.}"
# move the file count in the third argument or 20
limit="${3-20}"
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
echo mv "$file" "$dest"
let limit-=1
[[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
致电方式:
move-oldest /mnt/backup/ /var/log/foo/ 20
将最早的20个文件从/var/log/foo/
移到/mnt/backup/
。
请注意,我包括文件和目录。对于文件,仅将其添加-type f
到find
调用中。
感谢enzotib和ПавелТанков对这个答案的改进。
-n
。至少在我的版本中,它不能正确排序十进制数字。您必须删除日期中的点或使用-printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz
,ISO日期或其他方式。
.
必须与您无关的排序。)说起来很清楚sort -z -n -t. -k1
。
%TS
还会显示形式为的“小数部分” 00.0000000000
,因此也会丢失粒度。最近的GNU sort
可以通过使用-V
“版本排序” 来解决此问题,该版本将按预期处理这种类型的浮点。
%T@
也可以,因为它是零填充的。
这在zsh中最简单,您可以在其中使用Om
glob限定符按日期对匹配项进行排序(最旧的优先),而[1,20]
限定符仅保留前20个匹配项:
mv -- *(Om[1,20]) target/
D
如果您还想包括点文件,请添加限定符。添加.
,如果你想只匹配常规文件,而不是目录。
如果您没有zsh,请使用Perl单行代码(您可以用少于80个字符来完成此操作,但在清晰度方面会付出更多的代价):
perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'
仅使用POSIX工具,甚至是bash或ksh,按日期对文件进行排序是很麻烦的。您可以使用轻松地完成此操作ls
,但是解析输出ls
是有问题的,因此仅当文件名仅包含除换行符以外的可打印字符时,此方法才有效。
ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done
将ls -t
输出与tail
或组合head
。
简单示例,仅当所有文件名仅包含可打印字符(除了空白和)时才起作用\[*?
:
mv $(ls -1tr | head -20) other_folder
ls -1Atr
touch $'foo\n*'
。如果执行mv“ $(ls)”并且该文件坐在那里会发生什么?
您可以为此使用GNU find:
find -maxdepth 1 -type f -printf '%T@ %p\n' \
| sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
| xargs echo mv -t dest_dir
find在其中显示修改时间(从1970年开始,以秒为单位)和当前目录中每个文件的名称,输出将根据第一个字段进行排序,最旧的20个过滤并移至dest_dir
。echo
如果已经测试了命令行,请删除。
还没有人发布过一个bash示例,该示例可满足文件名中嵌入的换行符(嵌入任何内容)的需求,所以这里是一个。它移动3个最旧的(日期)常规文件
move=3
find . -maxdepth 1 -type f -name '*' \
-printf "%T@\t%p\0" |sort -znk1 | {
while IFS= read -d $'\0' -r file; do
printf "%s\0" "${file#*$'\t'}"
((--move==0)) && break
done } |xargs -0 mv -t dest
这是测试数据片段
# make test files with names containing \n, \t and " "
rm -f '('?[1-4]' |?)'
for f in $'(\n'{1..4}$' |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4 |\t)'
ls -ltr '('?[1-4]' |'?')'; echo
mkdir -p dest
这是检查结果片段
ls -ltr '('?[1-4]' |'?')'
ls -ltr dest/*
使用GNU最简单find
。我每天在Linux DVR上使用它来删除比一天还早的视频监视系统中的记录。
语法如下:
find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
请记住,这find
将一天定义为从执行时间起的24小时。因此,在11 pm修改的文件不会在1 am删除。
您甚至可以find
与结合使用cron
,因此可以通过以root身份运行以下命令来自动计划删除:
crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF
您始终可以find
通过参考手册页获取更多信息:
man find
由于其他答案不符合我和问题的目的,因此该外壳已在CentOS 7上进行了测试:
oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"