语境
我正在运行备份脚本,这些脚本创建 tar.gz 文件并将它们传输到我的无头 Linux 虚拟机上的 dropbox 文件夹中。
问题
我不知道如何创建一个脚本来检测根据方案命名的文件中最旧的文件/root/Dropbox/apache2-backup-%Y-%m-%d-%H-%M
。
然后我想把这个文件移到一个/root/Dropbox-archive/
文件夹中,因为 Dropbox 上只有 2GB 的空间。
答案1
“简单”的解决方案
ls -1 /Dropbox/apache2-backup-*
应按名称的字母顺序排序,即按日期排序(因为日期的格式是最有效的数字位于左侧)。然后,您可以通过管道获取最旧的文件head -1
(假设文件名中没有换行符)。一般来说,管道 fromls
是一个坏主意,但在这种情况下应该没问题,因为您知道文件名中没有特殊字符。
mv "$(ls -1 /Dropbox/apache2-backup-* | head -1)" /root/Dropbox-archive/
我可能还建议使用mv --no-clobber
(或mv -n
) 代替,这样您就不会意外覆盖文件。
“正确”的解决方案
如果您确实担心特殊字符,可以使用以下内容代替。
to_move="$(find /Dropbox -maxdepth 1 -type f -name 'apache2-backup-*' -print0 | sort -z | tr '\0\n' '\n\0' | head -1 | tr '\0\n' '\n\0')"
mv "$to_move" /root/Dropbox-archive/
解释
find /Dropbox -maxdepth 1 -type f -name 'apache2-backup-*' -print0
:返回普通文件 (-type f
) ,/Dropbox
而不进入-maxdepth 1
与模式 匹配的子目录 ( )-name 'apache2-backup-*'
。用空字符分隔,而不是换行符-print0
。| sort -z
:种类基于空字符(注意并非所有类型都可以这样做)。| tr '\0\n' '\n\0'
:交换null 和换行符由 head 处理。| head -1
: 返回第一行。| tr '\0\n' '\n\0'
:将空值和换行符交换回来。mv "$to_move" /root/Dropbox-archive/
: 行动起来!
或者,在一行中:
mv "$(find /Dropbox -maxdepth 1 -type f -name 'apache2-backup-*' -print0 | sort -z | tr '\0\n' '\n\0' | head -1 | tr '\0\n' '\n\0')" /root/Dropbox-archive/
答案2
如果您只搬动最年长的一, 用这个:
_mv(){ mv -- "$1" /root/Dropbox-archive/; }
_mv /Dropbox/apache2-backup-*
但你的标题并没有这么说。
由于处理ls
输出是一个坏主意,这是一个更好的方法:
if [ "$BASH" ]; then
# If you happen to use bash or any other shell that has some similar array:
move_first_things(){ mv -- "${@:0:50}" /root/Dropbox-archive/; }
else # Unfortunate POSIX way
# As long as copy-pasting is acceptable, loop-unrolling with
# `mv -- "$1" "$2" "$3"... /root/Dropbox-archive/` is better since it calls `mv` less.
move_first_things(){
local max=50 count=0
while [ "$count" -lt "$max" ]; do
mv -- "$1" /root/Dropbox-archive/
shift
: $((count = count + 1))
done
}
fi
由于通配符对输出进行排序,就像ls
:
move_first_things /Dropbox/apache2-backup-*
聚苯乙烯:这确实可以通过外部程序来完成(mikeserv,解释在这里)。只要多加小心和额外的混淆,人们就可以安全地完成它。但在这种情况下我仍然会使用漂亮的文件名。
聚苯硫醚:don_crist 提到了一些 zshism。 zsh 对匿名事物有很好的支持,比如函数、参数扩展和数组。仍然使用文件名方式(因为bash
没有这样的排序运算符),整个事情可以写成baks=( /Dropbox/apache2-backup-* ); mv "$baks" /root/Dropbox-archive/
(引用这样的数组只是给出它的第一个成员),或者对于多个事情,mv "${baks[@]:0:50}" /root/Dropbox-archive/
.嗯,这实际上看起来比那些包装函数要好一些。
答案3
( IFS=/ # split on /
set -ef /Dropbox/apache2-backup* # set arg array to last glob
for f in $(\ls -rtd "$@";echo /) # iterate over sorted/split array
do [ -z "${f##D*}" ] || # ignore dirname
mv "${1%/*}/${f%?}" \
"/root${1%/*}-archive" # do sorted mv
done # do && break to do only one mv
)
无论出于何种原因,人们似乎认为您无法使用 可靠地对参数列表进行排序ls
。我认为这是因为这些人试图用换行符分隔路径名。但问题是,路径名不会在换行符上进行分隔——它们从来没有。路径名分隔符/
- 这是路径名分隔符,因此换行符只会造成混乱。如果你想可靠地解析ls
输出,你必须正确地分割它。
答案4
该问题指定了最旧的文件。 ls -rt
列出按修改时间排序的文件,最旧的在前。除了标识备份文件的前缀之外,该答案不依赖于文件名格式。这里不考虑文件名的日期部分:
mv "$(ls -tr /root/Dropbox/apache2-backup-*| head -1)" /root/Dropbox-archive/
按文件时间排序并不比按文件名排序更好,反之亦然。这很大程度上是个人喜好,取决于具体情况和文化。
在这两种情况下,以及大多数情况下,将命令扩展括在双引号中会更安全,"
如下所示"$( ... )"
,它将处理备份文件文件名中的所有特殊字符(空格、标点符号、制表符等),但包含换行符的文件名除外。