考虑一个包含 N 个文件的目录。
我可以按字母顺序列出文件并按以下方式存储列表
ls > list
然后我想在同一目录中创建n个子目录,可以通过以下方式完成
mkdir direc{1..n}
现在我想将前 m 或前 5 (1-5) 个文件从 移动list
到direc1
,接下来的 5 个文件即 6-10 移动到 ,direc2
依此类推。
这对你们来说可能是一项微不足道的任务,但目前我无法做到。请帮忙。
答案1
list=(*) # an array containing all the current file and subdir names
nd=5 # number of new directories to create
((nf = (${#list[@]} / nd) + 1)) # number of files to move to each dir
# add 1 to deal with shell's integer arithmetic
# brace expansion doesn't work with variables due to order of expansions
echo mkdir $(printf "direc%d " $(seq $nd))
# move the files in chunks
# this is an exercise in arithmetic to get the right indices into the array
for (( i=1; i<=nd; i++ )); do
echo mv "${list[@]:(i-1)*nf:nf}" "direc$i"
done
测试后删除两个“echo”命令。
或者,如果您想每个目录包含固定数量的文件,这更简单:
list=(*)
nf=10
for ((d=1, i=0; i < ${#list[@]}; d++, i+=nf)); do
echo mkdir "direc$d"
echo mv "${list[@]:i:nf}" "direc$d"
done
答案2
#!/bin/sh
d=1 # index of the directory
f=0 # number of files already copied into direc$d
for x in *; do # iterate over the files in the current directory
# Create the target directory if this is the first file to be copied there
if [ $f -eq 0 ]; then mkdir "direc$d"; fi
# Move the file
mv -- "$x" "direc$d"
f=$((f+1))
# If we've moved 5 files, go on to the next directory
if [ $f -eq 5 ]; then
f=0 d=$((d+1))
fi
done
有用的参考:
- 算术扩展
$((…))
- 条件表达式
[ … ]
- 为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?为什么引号和
--
inmv -- "$x" …
答案3
d=0; set ./somedirname #init dir counter; name
for f in ./* #glob current dir
do [ -f "$f" ] && set "$f" "$@" && #if "$f" is file and...
[ "$d" -lt "$((d+=$#>5))" ] || continue #d<(d+($#>5)) or continue
mkdir "$6$d" && mv "$@$d" || ! break #mk tgtdir and mv 5 files or
shift 5 #break with error
done
上面的命令利用了 shell 的 arg 数组将字符串连接到其头部和尾部的能力。例如,如果您编写了一个函数:
fn(){ printf '<%s>\n' "42$@"; }
...并称其为:
fn show me
...它会打印:
<42show>
<me>
...因为您可以添加或附加到第一个或最后一个元素(分别)在 arg 数组中,只需将其引号括在您的前/附加字符串周围即可。
arg 数组在这里也有双重作用,因为它还充当计数器 - $#
shell 参数总是让我们确切地知道到目前为止我们已经堆叠了多少个元素。
但是......这是一步一步:
d=0; set ./somedirname
$d
每创建一个新目录,var 就会加一。这里它被初始化为零。./somedirname
是你喜欢的样子。不过,前缀./
很重要 - 它不仅可以将所有操作根植到当前目录,还允许您指定您想要的任何类型的名称(如果你想疯狂地使用换行符或以连字符开头,你可以安全地 - 但这可能是不可取的)。因为 argname 总是以./
no 命令开头,所以不会将其误解为命令行中的选项。
for f in ./*
- 这将开始一个循环(如果有的话)
*
当前目录中的匹配项。同样,每个匹配项都以 为前缀./
。
- 这将开始一个循环(如果有的话)
[ -f "$f" ]
- 验证每次迭代的匹配肯定是常规文件(或一个链接)和...
set "$f" "$@"
- 将 shell 数组中的匹配项堆叠在另一个前面。这样,
./somedirname
始终位于数组的尾部。
- 将 shell 数组中的匹配项堆叠在另一个前面。这样,
[ "$d" -lt "$((d+=$#>5))" ]
$d
如果数组元素超过 5 个,则加 1 ,"$@"
同时测试增量结果。
|| continue
- 如果任何一个
[ -f "$f" ]
set ...
[ "$d" -lt...
命令未返回 true,则循环将继续进行下一次迭代,并且不会尝试完成循环的其余部分。这样既高效和安全的。
- 如果任何一个
mkdir "$6$d"
- 因为该子句确保我们只有在our现在处于并且 的值刚刚增加 1 的
continue
情况下才能达到这一点。因此,对于匹配和移动的第一组 5 个文件,这会为第五个文件创建一个名为 和的目录,依此类推。重要的是,这个命令$# > 5
./somedirname
$6
$d
./somedirname1
./somedirname5
失败如果任何具有目标路径名的路径名已存在。换句话说,这个命令是仅有的如果确实不存在同名的目录,则成功。
- 因为该子句确保我们只有在our现在处于并且 的值刚刚增加 1 的
mv "$@$d"
- 这会扩展数组,同时将 的值附加
$d
到最后一个元素的尾部 - 这是目标目录名称。所以它扩展如下:
mv ./file5 ./file4 ./file3 ./file2 ./file1 ./somedirname1
- ...这正是您想要发生的事情。
- 这会扩展数组,同时将 的值附加
|| ! break
如果前两个命令中的任何一个由于任何原因未成功完成,则
for
循环break
将停止。发送的返回值!
的布尔值倒数break
(通常为零),因此break
返回 1。这样,如果前面的任何命令中发生任何错误,循环将返回 false。这很重要 -for
循环 - 与while/until
循环不同 - 并不意味着测试,仅意味着迭代。如果没有显式测试这两个命令的返回,shell 不一定会因错误而停止 - 并且set -e
可能会完全杀死父 shell。相反,这确保了有意义的返回,并且如果出现任何问题,循环将不会继续迭代。乍一看,这似乎是这里唯一会停止的答案,例如,如果
mkdir ./somedirname
不返回 true - 所有其他答案将继续循环(并且可能会重复该错误,或者更糟糕的是,将当前目录中的文件移动到现有目录中,并可能覆盖那里同名的其他文件)。在循环中使用任意文件名,您应该总是测试两者源文件是否存在和因为目标的存在。
shift 5
- 这
shift
会删除 shell arg 数组中的前 5 个元素 - 它将放回./somedirname
并$1
重置数组状态以进行下一次迭代。
- 这
答案4
使用这个 awk 程序,您可以创建 shell 命令,如果有疑问,可以提前检查它们是否正确......
awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }' list
如果您对输出管道没意见,则将整个命令放入sh
.另外,如果您想避免额外的文件“列表”,您可以动态创建它;那么整个程序将是......
ls | awk -v n=5 '{ printf "mv \"%s\" %s\n", $0, "direc" int((NR-1)/n)+1 }' | sh
如果更改设置 n=5,则可以定义 5 以外的其他值。
如果您还想动态创建目标目录,这里有一个变体......
ls | awk -v n=5 '
NR%n==1 { ++c ; dir="direc"c ; print "mkdir "dir }
{ printf "mv \"%s\" %s\n", $0, dir }
' | sh