많은 폴더를 만들고 그 안에서 몇 가지 작업을 수행하고 싶습니다. 폴더 이름은 for
루프에서 변수 로 정의한 여러 화학 원소의 배열을 기반으로 합니다 .
for Element in Cr Hf Mo Nb Ta Ti V W Zr
CrHfMoNb
, , ... 등의 문자가 포함된 CrHfMoTa
하위 폴더를 얻을 수 있도록 알파벳 순서로 4개 요소의 모든 순열에 대한 폴더를 원합니다. 이 작업을 수행하기 위해 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
이렇게 하면 원하는 폴더가 생성되지만 or ZrVN
및 like 와 같은 알파벳이 아닌 조합도 얻게 되므로 불필요한 폴더도 많이 생성됩니다 HfHfN
. 세 번째 줄에 if 문을 추가하면 중복 항목을 제거할 수 있습니다.
do [ "$Element" != "$Elemen" ] && mkdir "$Element""$Elemen"N
하지만 이러한 중복 폴더는 완전히 사라지지 않았지만 대신 내 디렉토리에서 "유령" 파일이 되었습니다. 즉, HfHfN
파일 확장자가 없이 etc로 호출되었습니다. 그러나 실제 문제는 나머지 폴더입니다. 다음과 같은 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)'
그러면 포함된 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에서도 매우 유사한 작업을 수행할 수 있을 것입니다...
재귀 셸 구현(재귀 셸 기능은 재미를 위해 매우 효율적인 경우가 거의 없음):
#!/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
읽은 생각Studog의 답변그리고 모든 면에서 영감을 얻습니다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개의 서로 다른 디렉터리가 생성되었으며 mkdir
.