find
나중에 외부 파일에서 제외할 디렉터리 목록을 읽을 수 있는 스크립트를 작성하려고 합니다 . 이 부분은 제가 직접 할 수 있지만, 귀찮은 어레이 확장으로 인해 작업이 어려워집니다. 먼저, 적절한 샘플 디렉터리 트리를 얻으려면 몇 가지 "준비"를 수행해야 합니다.
$ mkdir tmp && cd tmp
$ mkdir excl1_dirx excl2_dirx excl3_dirx
$ touch excl1_dirx/dummy1.txt excl2_dirx/dummy2.txt excl3_dirx/dummy3.txt
$ mkdir excl1_diry excl2_diry excl3_diry
$ touch excl1_diry/dummy4.txt excl2_diry/dummy5.txt excl3_diry/dummy6.txt
$ touch dummy00.txt dummy01.txt
스크립트가 유효하면 dummy00.txt
및만 표시됩니다.dummy01.txt
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=" . -type f ! ( "
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=" -o "
fi
find_str+=" -path \"./${excl_d[i]}/*\""
done
find_str+=" )"
# this is just for debugging
echo "[debug] value of str = find $find_str"
find $find_str
첫째, done
" " 앞의 줄이 왜 그렇게 (겉보기에) 복잡합니까? 글쎄요, bash
때때로 사용자가 기대하지 않는 일을 하여 사용자를 괴롭히는 것을 좋아합니다. 이 인용문이 없으면확장각 배열 요소의 예는 (분명히) 경계를 깨는 excl*_dirx
가 됩니다 . 각 배열 요소 내에서 한 쌍의 큰따옴표를 사용하고 있지만 이는 실제로 bash가 확장 헛소리를 하는 것을 방지하기 위한 것입니다!excl1 dirx excl2 dirx excl3 dirx
-path
그러나 최상의 결과는 아직 나오지 않았습니다. 끝에서 두 번째 줄( 이스케이프된 경우 (
)
)은 \(
\)
셸에서는 잘 작동하지만 독립 실행형 스크립트에서는 작동하지 않습니다. 오류가 발생하지 않더라도 후자의 구현 결과는 잘못된 것입니다.
작은따옴표와 큰따옴표를 사용하여 다양한 조합을 시도했습니다.
find_str+=" -path \"./${excl_d[i]}/*\""
좋아, 하지만 두 번째에서 마지막 행에 표시할 때 완전히 완벽해 보이더라도 작동시킬 수는 없습니다. bash
내부적으로 이스케이프된 따옴표와 이스케이프되지 않은 따옴표를 다르게 처리하는 것 같습니다 \"
. 아, 따옴표 안의 따옴표는 거의 모든 곳에서 작동합니다.하지만어떤 이유로 이 연산자를 사용할 때 이는 필터링됩니다 +=
.
저는 이 동작에 대한 설명뿐만 아니라 독립 실행형 스크립트에서 이를 작동시키는 방법에 대한 솔루션도 찾고 있습니다. 이건 내가 저지른 어리석은 실수임에 틀림없다. :-/
답변1
여기서 문제는 find_str
문자열을 가져와서 문자열 목록으로 사용하는 것입니다.jw013 님의 의견이 맞습니다, 그리고 읽어보세요명령을 변수에 넣으려고 했지만 복잡한 경우는 항상 실패합니다!. 전체 명령을 변수에 넣지 않지만 여러 단어를 문자열 변수에 넣으려고 하면 문제가 발생합니다.
Bourne/POSIX 쉘에서 이는 필요악입니다. 하지만 ksh/bash/zsh에는 더 좋은 방법이 있습니다. 바로 배열을 사용하는 것입니다.
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=( . -type f \! \( )
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=( -o )
fi
find_str+=( -path "./${excl_d[i]}/*" )
done
find_str+=( \) )
find "$find_str[@]}"
이러한 필터를 표현하는 더 간단한 방법이 있습니다.
#!/bin/bash
exclude_patterns=("excl*_dirx" "excl*_diry")
exclude_args=()
while [[ ${#exclude_patterns} -gt 0 ]]; do
exclude_args+=( -path "./${exclude_patterns[1]}/*" -prune -o )
shift exclude_patterns
done
find "${exclude_args[@]}" -type f
답변2
당신을 위한:
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=". "
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=" -o "
fi
find_str+=" -path \"./\${excl_d[i]}\" -prune "
done
find_str+=" -o -type f -print "
# this is just for debugging
echo "[debug] value of str = find $find_str"
eval "find ${find_str}"
여기서 문제는 bash가 스크립트의 한 줄을 실행할 때 find ${find_str}
하나의 인수로만 find를 실행한다는 것입니다. 즉, find의 주 함수의 argc 인수는 2와 같습니다. eval
대신, 문자열을 형성하고 bash에서 토큰화를 전달하세요.