我想创建大量的文件夹并在其中进行一些操作。文件夹名称基于几种化学元素的排列,我将它们定义为for
循环中的变量:
for Element in Cr Hf Mo Nb Ta Ti V W Zr
我想要一个文件夹,用于按字母顺序排列 4 个元素的所有排列,以便获得包含字母CrHfMoNb
、CrHfMoTa
、 ... 等的子文件夹。我尝试使用 4 个嵌套for
循环来完成此操作,但为了简单起见,我将在此处仅使用 2 个循环进行演示。我想出的代码是:
for Element in Cr Hf Mo Nb Ta Ti V W Zr; do
for Elemen in Hf Mo Nb Ta Ti V W Zr; do
mkdir "$Element""$Elemen"N # the N at the end is intended
done
done
这会产生我想要的文件夹,但也会产生很多不必要的文件夹,因为我还得到了像TiNbN
或ZrVN
这样的非字母组合以及像HfHfN
.我可以通过在第三行添加 if 语句来消除重复项
do [ "$Element" != "$Elemen" ] && mkdir "$Element""$Elemen"N
尽管这些重复的文件夹并没有完全消失,而是成为我的目录中的“幻影”文件,这意味着它们被称为HfHfN
等,但没有文件扩展名。然而真正的问题是其余的文件夹。我尝试添加更多 if 语句,例如
do [ "$Element" != "$Elemen" ] && [ "$Element" > "$Elemen" ] && mkdir "$Element""$Elemen"N
减少允许的排列数量,但这并不能消除任何东西。我还尝试将 if 语句分离到各自的 for 循环中,但这也不会改变任何内容:
for Element in Cr Hf Mo Nb Ta Ti V W Zr; do
[ "$Element" != "$Elemen" ] && [ "$Element" > "$Elemen" ] &&
for Elemen in Hf Mo Nb Ta Ti V W Zr; do...
我不完全确定这是否>
是正确的if
命令,但从这个列表中http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html这似乎是最合理的。使用类似的命令-ne, -lt, -le, -gt
也不起作用,因为它们需要整数,所以不接受字母。最后我想将4个循环组合在一起,这样就变得有点难以看透。我缺少什么?
答案1
#/bin/sh
# shellcheck disable=SC2046
# ^ word-splitting by the shell is intentional in this file
elems="Cr Hf Mo Nb Ta Ti V W Zr"
for a in $elems
do
for b in $elems
do
for c in $elems
do
for d in $elems
do
# for a set of any four elements:
# string them together, separated by NUL-bytes
# sort them lexicographically ...
# ... with NUL separating the elements (-z)
# ... and eliminate duplicates (-u)
# then replace the NUL bytes with line breaks
# allow the shell to split on those line breaks
# and chuck the resulting chunks into $1, $2, etc
set -- $(printf '%s\0' "$a" "$b" "$c" "$d" | sort -z -u | tr "\0" "\n")
# only if the current selection of elements consisted of four
# different ones (remember we eliminated duplicates):
if [ $# -eq 4 ]
then
# create a directory, don't error out if it already exists (-p)
mkdir -p "$(printf '%s' "$@")"
fi
done
done
done
done
效率不高(sort
甚至调用明显的非候选者和多次mkdir
调用同一目录名),但内部循环最多 9 4 = 6561 次迭代,而且它是一次性脚本,我认为不会这是值得花费大量时间进行优化的。
编辑:
Xeon E3-1231v3 的基准测试,没有mkdir
:
./elemdirs.sh > /dev/null 11.66s user 1.73s system 173% cpu 7.725 total
并随之而来:
./elemdirs.sh > /dev/null 13.80s user 2.16s system 156% cpu 10.215 total
它产生 126 个目录,预期数量组合其中 k = 4,n = 9。
答案2
使用 Perl 和Algorithm::Combinatorics
模块:
perl -MAlgorithm::Combinatorics=combinations -e '$"=""; map { mkdir "@{$_}N" } combinations([qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4)'
这将创建 126 个目录,您可以从包含的四个单词的所有组合中获得这些目录。每个目录的名称N
末尾都有一个。由于代码中数组的初始排序,各个单词将始终按字母顺序出现在目录名称中。
作为一个正确的 Perl 脚本:
#!/usr/bin/perl
use strict;
use warnings;
use English;
use Algorithm::Combinatorics qw(combinations);
# When interpolating a list in a string (@{$ARG} below), don't use a delimiter
local $LIST_SEPARATOR = "";
# Get all combinations, and create a directory for each combination
map { mkdir "@{$ARG}N" } combinations( [qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4 );
这几乎可以立即运行,并且可以轻松扩展以包含更多单词或组合长度。
你可能可以在 Python 中做一些非常类似的事情......
递归 shell 实现(只是为了好玩,递归 shell 函数很少非常有效):
#!/bin/sh
build_combinations () {
set_size=$1
shift
if [ "$set_size" -eq 0 ]; then
printf 'N'
else
for token do
shift
for reminder in $(build_combinations "$(( set_size - 1 ))" "$@")
do
printf '%s%s\n' "$token" "$reminder"
done
done
fi
}
build_combinations 4 Cr Hf Mo Nb Ta Ti V W Zr | xargs mkdir
读过的想法斯图狗的回答以及来自各个方面的灵感StackOverflow 问题的答案。
请注意,此解决方案的优点是目录名称始终以N
.递归停止分支输出N
而不是空字符串,这使得整个事情正常进行。如果没有它(打印空字符串或换行符),带有命令替换的循环将没有任何内容可循环,并且不会有输出(由于变量的默认值IFS
)。
答案3
对 @n.st 答案的改进,利用了元素一开始就按排序顺序的事实。我认为这也更清楚一些。
#!/bin/bash
elements=(Cr Hf Mo Nb Ta Ti V W Zr)
len=${#elements[@]}
(( a_end = len - 3 ))
(( b_end = len - 2 ))
(( c_end = len - 1 ))
(( d_end = len - 0 ))
(( a = 0 ))
while (( a < a_end )); do
(( b = a + 1 ))
while (( b < b_end )); do
(( c = b + 1 ))
while (( c < c_end )); do
(( d = c + 1 ))
while (( d < d_end )); do
mkdir "${elements[$a]}${elements[$b]}${elements[$c]}${elements[$d]}"
(( d++ ))
done
(( c++ ))
done
(( b++ ))
done
(( a++ ))
done
每个内部循环的关键部分从封闭循环的下一个元素索引开始。这是生成项目列表的所有组合的非常常见的模式。
运行:
user@host:~/so$ time ./do.sh
real 0m0.140s
user 0m0.085s
sys 0m0.044s
和
user@host:~/so$ ls -1d Cr* Hf* Mo* Nb* Ta* Ti* V* W* Zr* | wc -l
ls: cannot access 'V*': No such file or directory
ls: cannot access 'W*': No such file or directory
ls: cannot access 'Zr*': No such file or directory
126
答案4
花几个步骤来跳过冗余。它将加快整个过程。
declare -a lst=( Cr Hf Mo Nb Ta Ti V W Zr ) # make an array
for a in ${lst[@]} # for each element
do for b in ${lst[@]:1} # for each but the 1st
do [[ "$b" > "$a" ]] || continue # keep them alphabetical and skip wasted work
for c in ${lst[@]:2} # for each but the first 2
do [[ "$c" > "$b" ]] || continue # keep them alphabetical and skip wasted work
for d in ${lst[@]:3} # for each but the first 3
do [[ "$d" > "$c" ]] || continue # keep them alphabetical and skip wasted work
mkdir "$a$b$c$d" && echo "Made: $a$b$c$d" || echo "Fail: $a$b$c$d"
done
done
done
done
冗余跳过适用于后面的循环开始时,例如当外部循环位于元素 4 上但第二个循环仍在元素 3 或 4 上时。它们会跳过这些,因为它们不是字母组合。这样做也保证不会重复。这在我的笔记本电脑上的 git bash 中生成了 126 个不同的目录,在 0m8.126s 中没有错误,除了mkdir
.