所以愚蠢而没有耐心的我在 19.04 服务器上使用了以下脚本,试图将一堆视频文件移动到带有前缀的文件夹中:
dirs=(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
shopt -s nocasematch
for file in *
do
for dir in "${dirs[@]}"
do
if [ -d "$file" ]; then
echo 'this is a dir, skipping'
break
else
if [[ $file =~ ^[$dir] ]]; then
echo "----> $file moves into -> $dir <----"
mv "$file" "$dir"
break
fi
fi
done
done
不知道哪里出了问题,但是它没有将文件移动到文件夹,而是移动到了单一的输出...所以:
----> a1.ts moves into -> A <----
----> a2.ts moves into -> A <----
----> a3.ts moves into -> A <----
----> a4.ts moves into -> A <----
----> a5.ts moves into -> A <----
----> c1.ts moves into -> C <----
----> c2.ts moves into -> C <----
----> c3.ts moves into -> C <----
----> c4.ts moves into -> C <----
----> c5.ts moves into -> C <----
幸运的是,当我注意到它没有按预期进行时,我立即停止了该过程(CTRL + C),并且没有浏览整个文件夹。
所以现在我得到了这些文件A
和C
,它们的大小不到 1Gb,而且看起来它们是一个单独的视频。
文件夹本身的总磁盘使用量中有 50Gb 未被占用,但计算机的整体磁盘空间保持不变。这让我认为文件没有被删除?
任何帮助表示感谢,谢谢:)
编辑:文件实际上已经消失了,只有最后写入的文件保留了下来,只需要花一些时间来更新磁盘使用信息..故事的寓意是,先在模拟文件上运行你的脚本!
答案1
我认为这是问题所在:您应该创建目录 A、B、C...Z。如果您这样做了,命令mv
应该将文件移动到这些目录中。
但如果不是,mv
命令会将文件移动到具有这些名称的文件中,A,B,C......我认为这就是你所做的。
为了使 shellscript 更安全,您应该在开始移动之前让它创建目录(如果它们尚不存在)。
dirs=(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
for dir in "${dirs[@]}"
do
mkdir -p $dir
done
如果你希望事情变得更加安全,你也可以使用mv
选项-i
-i, --interactive
prompt before overwrite
答案2
@Sudodus 已经解释过了出了什么问题,但这是下次使用的更简单的脚本版本:
for letter in {a..z}; do
dir=${letter^}
mkdir -p -- "$dir"
mv -- "$letter"* "${letter^^}"* "$dir"/
done
解释
for letter in {a..z}; do
:扩展为和{a..z}
之间的所有小写字母:a
z
$ echo {a..z} a b c d e f g h i j k l m n o p q r s t u v w x y z
因此这将遍历所有小写字母,并将每个字母保存为
$letter
。dir=${letter^}
:语法${var^^}
返回变量的内容$var
,其中第一个字符为大写(因为这只有一字符,这就是我们所需要的)。因此,如果$letter
是a
,则${letter^^}
是A
,因此$dir
将是当前 的大写版本$letter
。mkdir -p -- "$dir"
:创建目录。如果目录已存在,则不执行任何操作(-p
)。--
表示选项的结束,对于防止以 开头的名称很有用-
。mv -- "$letter"* "${letter^}"* "$dir"
:将每个文件(或目录)移动到相关目标。
问题在于,它还会移动您可能拥有的任何目录。它不会移动目标目录,因为它们要么尚不存在,要么您会尝试将它们移动到自身中,但任何非目标目录的现有目录都将被移动。
如果这是一个问题,你必须做这样的事情:
for file in *; do
if [[ ! -d "$file" ]]; then
letter="${file:0:1}"
dir="${letter^}"
mkdir -p -- "$dir"
mv -- "$file" "$dir"/
fi
done
答案3
您可以根据模式对文件进行匹配,而不必根据字典数组检查每个文件,这会产生很多迭代。
非常基本的排序:
#!/bin/bash
videos=./videos
sorted=./sorted
# sort types link,move.
sort_type=link
find "$videos" -maxdepth 1 -type f \
\( -name '*.avi' -o -name '*.mkv' -o -name '*.mp4' \) -print0 |
while IFS= read -r -d ''; do
b=$(basename "$REPLY")
c=${b::1}
case $c in
[a-zA-Z]) label=${c^} ;; [0-9]) label="0-9" ;; *) label="_" ;;
esac
[[ ! -d "$sorted/$label" ]] && mkdir -p "$sorted/$label"
if [[ -L $sorted/$label/$b ]] || [[ -e $sorted/$label/$b ]]; then
echo "File/link: '$b' exists, skipping."
continue
fi
case $sort_type in
link)
ln -rfst "$sorted/$label" -- "$REPLY"
;;
move)
mv -t "$sorted/$label" -- "$REPLY"
;;
esac
done
答案4
以下是一些阻止mv
覆盖现有文件的方法:
如果要移动到目录,请在目标后附加一个斜杠,即使用
mv "$file" "$dir"/
而不是mv "$file" "$dir"
。如果$dir
不存在或不是目录,mv
则会抱怨:$ touch a $ mv a z/ mv: cannot move 'a' to 'z/': Not a directory $ touch z $ mv a z/ mv: failed to access 'z/': Not a directory
这似乎使得系统调用
rename("a", "z/")
,所以如果有人同时处理同一组文件,它应该不会受到检查时到使用时漏洞的影响。或者,使用。同样,如果它不是目录,
mv -t "$dir" "$file"
它会抱怨。$dir
使用该
-n
选项防止覆盖现有文件:-n, --no-clobber do not overwrite an existing file
它不会阻止重命名第一个文件,但不会将其与其他文件一起丢弃。
这似乎调用了一个简单的
rename()
,因此同时处理可能不安全。(有renameat2()
它将支持一个标志来防止覆盖。)