"sh -c"에 대한 인라인 쉘 스크립트가 예상대로 실행되지 않습니까?

"sh -c"에 대한 인라인 쉘 스크립트가 예상대로 실행되지 않습니까?

find디렉토리에서 명령을 실행하고 발견된 항목을 기반으로 출력 파일을 생성하려고 합니다 .

이것이 내가 지금까지 가지고 있는 것입니다:

docker2:~/src/docker# cat test.sh
#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;

그러나 이 명령의 출력은 다음과 같습니다.

Full path: /data/nc
Basename: /data/nc
Full path: /data/src
Basename: /data/src
Full path: /data/sql0
Basename: /data/sql0
Full path: /data/proxy
Basename: /data/proxy

백틱이 작동해야 한다는 것을 알고 있습니다.

docker2:~/src/docker# cat test.sh
#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     echo \"Date: `date`\"; echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;

다음을 제공합니다:

Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/nc
Basename: /data/nc
Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/src
Basename: /data/src
...

그리고 변수에도 문제가 있습니다.

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     export THIS_DIR={}; THIS_DIRECTORY={}; echo \"$THIS_DIR $THIS_DIRECTORY\";
" \;

이 명령의 출력은 비어 있으며 의 각 디렉터리에 대해 빈 줄이 있습니다 /data. 즉, 내보낼 때에도 변수가 저장되지 않습니다.

내가 뭘 잘못했나요? 나는 이것이 이상한 중첩이나 작업 순서 또는 이상한 쉘 코드와 관련이 있다고 추측합니다.

답변1

이는 `basename {}`콘텐츠가 큰따옴표로 묶여 있기 때문에 예상되는 현상입니다. 이렇게 하면 호출하기 전에 실행되어 find문자열 대체 명령으로 대체됩니다 {}. 이는 변수가 실제로 설정되기 전에 확장되는 이후 예제에서도 문제가 됩니다.

를 사용할 때 sh -c인수로 사용되는 스크립트는 -c실제로 항상 작은따옴표로 묶어야 합니다. 이렇게 하면 스크립트 내에서 인용이 더 간단해집니다. 인라인 스크립트에 대한 모든 인수는 코드에 삽입된 텍스트가 아닌 명령줄에서 전달되어야 합니다(""find -exec sh -c"를 사용해도 안전합니까?"이유로). 귀하의 경우:

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    echo "Full path: $1"
    echo "Basename: $(basename "$1")"' sh {} \;

또는 몇 가지 변수를 사용하고 를 사용하도록 전환합니다 printf.

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    pathname=$1
    filename=$(basename "$pathname")
    printf "Full path: %s\n" "$pathname"
    printf "Basename: %s\n" "$filename"' sh {} \;

다음 을 사용하여 인라인 스크립트를 sh -c실행할 때 이는 일반적으로 쉘이나 스크립트의 이름 이므로 해당 문자열을 전달한 다음 발견된 경로 이름을 전달합니다 .$0$1$2$0sh$1

또는 보다 효율적으로 가능한 한 적은 수의 호출을 사용 sh -c하고 매개변수 대체를 사용합니다.

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    for pathname do
        printf "Full path: %s\n" "$pathname"
        printf "Basename: %s\n" "${pathname##*/}"
    done' sh {} +

여기서는 find가능한 많은 인수를 사용하여(가능한 적은 횟수) 인라인 스크립트를 호출하도록 준비합니다. 각 호출에서 는 $0before 로 설정되고 sh다른 위치 매개변수는 발견된 경로명에서 해당 값을 가져옵니다. 루프는 전달된 경로 이름( $0위치 인수가 아니므로 반복되지 않음)을 반복합니다.

귀하의 테스트는 디렉토리 자체를 ! -name .찾는 것을 피하는 것이라고 가정합니다 . /data이미 건너뛰었으므로 필요하지 않습니다 -mindepth 1( /data경로는 검색 경로의 최상위이므로 "깊이 0"에 있습니다). 또한 find항목은 디렉터리에서 반환되지 않으므로 .항목을 사용하지 않은 경우 테스트에서는 해당 항목을 삭제하지 않습니다. 이 경우 를 사용할 수 있습니다./data-mindepth 1! -path /data

단일 디렉토리 내의 디렉토리를 반복하고 있고 다음을 사용했기 때문입니다.태그, 그리고 타임스탬프 등에 대한 멋진 테스트를 수행하는 데 익숙하지 않기 때문에 find기본 쉘 루프를 수행할 수 있습니다.

#!/bin/bash

shopt -s dotglob nullglob

for pathname in /data/*; do
    if [[ -d $pathname ]] && [[ ! -L $pathname ]]; then
        printf 'Full path: %s\n' "$pathname"
        printf 'Basename: %s\n' "${pathname##*/}"
    fi
done

여기서는 경로명과 이름에 대한 정보를 인쇄하기 전에 해당 /data이름이 디렉터리인지 여부를 명시적으로 테스트합니다. 의 쉘 dotglob옵션은 숨겨진 이름에 대한 와일드카드 패턴 일치를 bash허용하고 *비어 있거나 패턴이 아무것도 일치하지 않는 경우 nullglob루프 양식이 실행되는 것을 완전히 방지합니다 ./data

바라보다""find"의 -exec 옵션 이해find" 와 함께 사용하는 방법에 대한 일반 정보입니다 -exec.

관련 정보