![如何从包含 80 GB 文件的文件夹中移动 30 GB 的文件?](https://linux22.com/image/1072927/%E5%A6%82%E4%BD%95%E4%BB%8E%E5%8C%85%E5%90%AB%2080%20GB%20%E6%96%87%E4%BB%B6%E7%9A%84%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%AD%E7%A7%BB%E5%8A%A8%2030%20GB%20%E7%9A%84%E6%96%87%E4%BB%B6%EF%BC%9F.png)
我有一个文件夹,里面有 500 多个视频,文件夹大小约为 80 GB。
我想将 30GB 的视频移动到另一个文件夹,但是该怎么做呢?
我正在使用终端,无法安装 GUI。
答案1
不加选择地(自动地)
我不知道有哪个命令行工具可以做到这一点...但是,您可以使用脚本移动文件,直到移动了一定的总大小,例如像这样(只运行一次脚本...如果再次运行它,它将再次从剩余文件中移动指定的大小):
#!/bin/bash
sourc_dir="/path/to/directory" # Set the path to the source directory "no trailing /"
dest_dir="/path/to/directory" # Set the path to the destination directory "no trailing /"
move_size="30" # Set the total size of files to move in Gigabytes
# Don't edit below this line
move_size=$(( move_size * 1000000 ))
current_size=$(du -sk "$sourc_dir" | awk '{printf "%d", $1}')
remaining_size=$(( current_size - move_size ))
for f in "$sourc_dir"/*; do
[ -f "$f" ] || continue
new_size=$(du -sk "$sourc_dir" | awk '{printf "%d", $1}')
(( new_size <= remaining_size )) && exit
mv -n -- "$f" "$dest_dir"/
done
甚至一行(无需脚本文件)您可以在终端中运行,例如:
for f in /path/to/source/*; do [ -f "$f" ] || continue; (( size >= (30*(1000**3)) )) && break || (( size += $(stat --printf "%s" "$f") )) && mv -n -- "$f" /path/to/destination/; done
您可能希望unset size
能够在同一个 shell 实例中重新运行它。
选择性地(手动)
您可以随时根据文件名、大小、类型等从搜索工具中指定 shell 模式或管道。
或者,更方便的是,您可以使用基于控制台的文件管理器,该管理器支持多个文件选择,例如众所周知且功能丰富的GNU 午夜指挥官可从 Ubuntu 官方存储库获取并按如下方式安装:
sudo apt install mc
然后输入mc
并点击来运行它Enter...然后您将看到一个可导航的用户界面,您可以在其中使用Insert键盘键选择多个文件...您将能够实时看到所选文件的总大小及其数量,如下所示:
选择完要移动的所有文件后,按F6键盘键打开移动/重命名对话框并输入目标目录,然后选择[< OK >]
一次移动选定的文件,如下所示:
或者甚至不太知名、功能较少fff(超快的文件管理器):-) ... 这写得很有趣bash
... 虽然它支持多个文件选择,但您可能需要稍微定制一下才能看到选定文件的总大小,例如作为快速补丁,您可以从此更改159
源文件中的行:fff
local mark_ui="[${#marked_files[@]}] selected (${file_program[*]}) [p] ->"
更改为:
local mark_ui="[${#marked_files[@]}] $(for f in "${marked_files[@]}"; do (( tsize += $(stat --printf "%s" "$f") )); done; tsize=$((tsize/(1000**2))); printf '[%d M]' "$tsize";) selected (${file_program[*]}) [p] ->"
然后您可以像这样简单地运行该文件(无需安装):
bash fff
或者查看使用帮助如下:
bash fff -h
然后,您要做的就是导航到源目录并开始选择要移动的文件m(小写) 键盘按键如下:
选择完文件后,导航到目标目录,然后通过点击p(小写)键盘键。
答案2
这是替代方案拉法的想法是不需要du
对整个目录的每个文件进行。相反,它对stat
每个文件进行一次,使用find
来-printf
%s
获取大小并%p
获取要移动的文件的路径。
#!/bin/bash
src='foo'
dest='bar'
# 30GB in bytes to move:
(( bytestomove=30*(1000**3) ))
movedbytes=0
while IFS=$'\t' read -r -d '' bytes file
do
# if we already moved enough, break out
(( movedbytes >= bytestomove )) && break;
# try to move it, break out if it fails
mv -n -- "$file" "$dest" || break
# successfully moved, add bytes to movedbytes
(( movedbytes += bytes ))
done < <(find "$src" -type f -printf "%s\t%p\0")
(( movedmegs=movedbytes/(1000**2) ))
printf "Moved %d MB\n" $movedmegs
答案3
@TedLyngmo 指出,我首先提出的 tar 解决方案有效地复制了文件,但并未移动它们。这种区别很重要,原因有二:
- 复制后,源文件仍保留,而移动后,源文件不再存在于其源位置。
- 如果文件在同一个文件系统内移动,这个问题也可能出现这种情况,则
mv
只需将源文件重新链接到新位置,而不复制内容。这通常比复制要快得多,尤其是对于大文件。
移动场景
我们仍然可以使用tar
最初给出的复制场景中的概念来构建仅包含找到的前 30GB 文件的文件列表tar
,然后仅移动这些文件:
#!/bin/bash
# Convert human-readable number, e.g. 1M, 30G to number of 1024 byte blocks
limit=$(numfmt --from=auto $1 --to-unit 1024)
src_dir=$2
dest_dir=$3
mkdir $dest_dir
filelist=$(readlink -f $(mktemp -p $dest_dir))
# create a list of files to be moved. tar -v outputs filenames to stdout, and
# the dest archive is /dev/null
tar -v -L $limit -F false -c -f /dev/null -C $src_dir . > $filelist 2> /dev/null
tar_result=$?
if [ "$tar_result" = 2 ]; then
echo "$1 limit was hit - some unmoved files will remain in $dest_dir"
else
echo "All files will be moved"
fi
pushd $dest_dir > /dev/null
abs_dest_dir=$PWD
# create directories in the destination
grep '/.*/$' $filelist | xargs mkdir -p
popd > /dev/null
pushd $src_dir > /dev/null
# create hard links in the destination
grep -v '/$' $filelist | xargs cp -l --parents -t $abs_dest_dir
# unlink source files
grep -v '/$' $filelist | xargs rm
# remove source directories in reverse order, if they are empty
grep '/.*/$' $filelist | tac | xargs rmdir --ignore-fail-on-non-empty
popd > /dev/null
rm $filelist
这比我想要的更复杂:
mv
无法对多个文件的源路径和目标路径进行太多控制(特别是没有类似cp
--parents 选项),因此文件移动是通过在目标位置创建硬链接,然后取消链接源文件来实现的由于源目录包含要移动和不移动的条目,因此必须在目标中重新创建目录,而不是简单地移动(连同所有内容)。因此,在移动文件之前会创建相应的目标目录,如果相应的源目录为空,则会在移动文件之后删除它们。
注意事项
tar -v
每行输出一个文件条目。如果路径名中有特殊字符(例如换行符),则此操作将失败。希望源路径名的命名合理。这将移动少于 30GB 的实际文件数据,因为流包含元数据(文件名、时间戳等)以及实际内容,并且都受 30GB 限制。
我在这里对 KB 和 kb 的关系有点不拘一格。由于该
-L
参数以 1024 字节为单位,因此这实际上将移动限制为 3,072,000,000 字节的 tar 流。请根据需要调整数字。在当前目录中创建了 filelist.txt。此操作需要写入权限,这可能是理所当然的,因为移动操作无论如何都需要写入权限。
复制场景
您可以使用tar
将文件夹转换为流,然后tar
再次将流重新转换为新位置的文件。第一个tar
用法-L
将“磁带长度”限制为 3000000 KB,然后再请求新的磁带。 tar
使用 -F 在每个“磁带”之后运行false
。由于false
返回错误,传输在第一个“磁带”之后停止,即 30GB。让所有数据仅经过 2 次 tar 调用应该比使用 bash 循环遍历所有文件更快。
mkdir dest.dir
tar -cv -L 3000000 -F false -C src.dir . | tar -x -C dest.dir/
这将复制文件直到达到 30GB 的限制,然后tar
会出现错误:
tar: Unexpected EOF in archive
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now
tar: ‘false’ command failed
tar: Error is not recoverable: exiting now
tar
将不会传输达到限制时正在传输的文件 - 它根本不会被写入目的地。
注意事项
这将复制少于 30GB 的实际文件数据,因为流包含元数据(文件名、时间戳等)以及实际内容,并且都受 30GB 限制。
我在这里对 KB 和 kb 的关系有点不拘一格。由于该
-L
参数以 1024 字节为单位,因此这实际上将传输限制为 3,072,000,000 字节的 tar 流。请根据需要调整数字。
此答案的先前编辑使用了 find-cpio-head-cpio,然后是 tar-head-tar。现在提供的 tar-tar 解决方案应该足够了,但如果对其他解决方案感兴趣,请检查编辑历史记录。
答案4
转到文件所在的文件夹:
cd SOURCE_FOLDER
列出该文件夹中的文件列表
ls > list.txt
在任何编辑器中编辑它并删除您不想移动的文件。保存结果,这样 list.txt 现在只包含您要移动的文件。现在运行这个简单的脚本,它从 list.txt 文件中读取每一行并将该文件移动到您的目标目录。
while IFS= read -r line; do mv "$line" ../DESTINATION_DIRECTORY/"$line"; done < list.txt