我正在编写一个 bash 脚本来每天备份我的数据库(在 Mac OS X 上),并且我想保存最新的 7 个备份目录。以下是我的脚本中存在问题的片段(用于列出除最新目录之外的所有内容):
# Assigning a value to a variable named NUMBEROFFOLDERS.
NUMBEROFFOLDERS=7
# Command that leaves the newest NUMBEROFFOLDERS and removes everything else.
TOBEDELETED=`(ls -t|head -n $(($NUMBEROFFOLDERS));ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm -rfv`
# Executing the TOBEDELETED command.
eval "$TOBEDELETED"
PS 当我在终端窗口中输入 TOBEDELETED 中指定的命令时,该命令有效,但当我运行脚本时什么也没有发生。
编辑:i)脚本应该以不同的方式列出特定目标中的所有目录两次;sort 应该解析它们,并且只有最新的目录才会从列表中删除;sed 应该正确处理所有空格,rm -rfv 将从列表中删除所有目录(要删除的目录)及其所有文件。
ii) 该脚本应该保留最新的$NUMBEROFFOLDERS 文件夹。
iii) 我没有收到错误。我相信问题出在我引用分配命令的变量时。
答案1
一个不太优雅的工作解决方案(检查你的环境:
让我们从一个分而治之方法,因为它更清晰:
ListToSave=$(ls -dt */ |head -n $NUMBEROFFOLDERS| cut -f1 -d'/';)
此行将要保存的文件夹列表存储在变量中ListToSave
,也许稍后在脚本中可回收利用......
echo "$ListToSave"| awk 'BEGIN{printf("find . -maxdepth 1 -not -path \".\" ")}\
{printf ("-not -path \"./"$0"\" ") } \
END{print " -exec /bin/rm -rvf \"{}\" \\; "}' | /bin/sh
使用这个 3 行命令,您可以构造一个find
命令来删除除要保存的目录之外的所有内容,然后将其通过管道传输到 shell(在本例中为/bin/sh
)。
您可以检查命令构造,清除到 shell 的管道| /bin/sh
。
出于安全原因,使用完整路径运行命令总是更安全。(最好/bin/rm
是rm
,只是为了避免一些木马)
评论:当您计划编写脚本时,解析输出是不安全的ls
。通常情况下,您从有限且受控的情况开始;一段时间后,当您已经忘记它时,您应该尝试在更复杂且受控程度更低的情况下使用它。当您最终确信一切正常时......它发生了一些事情,而您意识到为时已晚。
笔记:
- 解析输出是不安全的
ls
...点击此处 ls -dt */ | cut -f1 -d'/'
应该只列出目录,甚至不列出文件。xargs
立即执行命令行,正如您可以从其手册页中看到的那样xargs-从标准输入构建并执行命令行
我强烈建议避免做以下事情。
但是如果你想解析,ls
你可以从之前的尝试中写出与此类似的最小修改
TOBEDELETED=`(ls -dt */ |head -n $NUMBEROFFOLDERS | cut -f1 -d'/'; ls)| sort | uniq -u | xargs echo "rm -rfv"`
这样,您将拥有存储的输出,TOBEDELETED
可以在编写脚本时稍后执行。
笔记: 它不适用于种类文件名如空格、、\t
... \n
,并且它可能会产生无法预料的副作用。假设您必须删除目录“1 bis”,但不能删除它所在的目录“1”。您将删除目录“1”,并且如果目录“bis”不存在,则在尝试删除目录“bis”时会出现错误。
答案2
你没有做你认为你在做的事情
# Command that leaves the newest NUMBEROFFOLDERS and removes everything else.
TOBEDELETED=`(ls -t|head -n $(($NUMBEROFFOLDERS));ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm -rfv`
反引号表示管道实际已执行,并且输出命令rm
被捕获到变量中。此输出不不是包含命令,它只是rm -v
输出。
我会简化一下这个命令:
stat -f "%m %N" * | sort -nr | sed -e "1,${numfolders}d" | cut -d " " -f 2 | xargs rm -rfv
我没有 BSD 系统可以玩,所以我不确定stat
格式字符串是否正确。
答案3
我添加了另一个答案测试环境构造函数。我创建了一个此处共享可执行项目。
按照链接在 shell 中写入bash main.sh
。它起作用了。将脚本复制到您的系统上并检查它是否起作用。如果不行,请发布您收到的错误。
#!/bin/bash
printf "\n\n\n# Let's we create your test directory\n"
TESTDIR="tmptmp"
rm -rf $TESTDIR
mkdir $TESTDIR
cd $TESTDIR
printf "\n### Now we are in \n"
pwd
mkdir -p 1 2 3 4 5 6 7 8 9 "1 bis";
printf "\n### Here the list in the creation order\n"
ls -dt */
sleep 1.5s # Take your time ...
touch 1 2 3 4 5 6 8; # Make those the last modified
printf "\n### Here the list with modified order\n"
ls -dt */
printf "\n\n# Let's go with your script \n\n"
#
# It follows your script. You can put it where you prefer but not in the
# directory that you want to delete else you can delete it too...
# If you put in the upper directory you can run it with:
# /bin/bash ../myscript.sh
#
# If you do one time `chmod u+x myscript.sh` you can execute it with
# ../myscript
# Remember to put as a 1st line "#!/bin/bash"
# Note: check the path of all the executable...
#
#!/bin/bash
NUMBEROFFOLDERS=7
ListToSave=$(ls -dt */ |head -n $NUMBEROFFOLDERS| cut -f1 -d'/';)
printf "This is the list of directories to be saved:\n%s\n" "$ListToSave"
printf "\n# That's the command that you'll execute piping it into a shell\n"
echo "$ListToSave" \
| awk 'BEGIN{printf("find . -maxdepth 1 -not -path \".\" ")}\
{printf ("-not -path \"./"$0"\" ") } \
END{print " -exec /bin/rm -rvf \"{}\" \\; "}'
sleep 1 ;
printf "\n# NOW we execute those commands with \"the power of piping\" \n\n"
echo "$ListToSave" \
| awk 'BEGIN{printf("find . -maxdepth 1 -not -path \".\" ")}\
{printf ("-not -path \"./"$0"\" ") } \
END{print " -exec /bin/rm -rvf \"{}\" \\; "}' | /bin/sh
printf "\n# That's the list of the survived as 'ls -d *' says to us\n\n"
ls -d *
printf "\n\n ### It worked like a charm ###\n\n"
exit 0
注意:如果你用它执行bash -f main.sh
它将不起作用,因为你会要求bash
避免路径名扩展。在 中搜索man bash
。