find 및 -exec를 사용하여 파일을 찾고 상위 파일에 대한 심볼릭 링크를 만듭니다.

find 및 -exec를 사용하여 파일을 찾고 상위 파일에 대한 심볼릭 링크를 만듭니다.

find특정 패턴과 일치하는 파일 찾기를 사용하고 상위 디렉토리를 다른 디렉토리에 심볼릭 링크하려고 합니다. 이것이 현재 스크립트입니다. (저는 Mac에서 이 작업을 수행하고 있으므로 여기서는 -printf가 작동하지 않습니다.)

#!/usr/bin/env bash

PROCESS_DIR="/Users/me/Google Drive/Me"
OUTPUT_DIR="/Users/me/OfflineFolder"

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \;

하지만 작동하지 않는 것 같습니다. 그러나 이 스크립트는 작동합니다(내 컴퓨터의 모든 md 파일에 대한 심볼릭 링크를 만듭니다:

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";
find . -type d -exec mkdir -vp "$OUTPUT_DIR/{}" \;
find . -name *.md -exec ln -vs "$(pwd)/{}" "$OUTPUT_DIR/{}" \;
find "$OUTPUT_DIR" -type d -empty -delete

왜 이것이 작동하지 않는지 아시나요? 나는 몇 가지 다른 접근 방식을 시도했습니다( find $PROCESS_DIRECTORY대신 사용 포함 cd $PROCESS_DIRECTORY). 감사해요

답변1

두 가지 질문이 있습니다. 둘 다 일이 발생한 순서와 관련되어 있습니다.

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \;단일 명령입니다. 쉘은 이를 실행하기 전에 이를 구문 분석합니다. 구문 분석 단계에서:

  • $(…)은 명령 대체이므로 dirname {}실행되어 결과가 나타납니다 .( 의 디렉토리 부분 없음 {}).
  • $OUTPUT_DIR변수 대체이므로 해당 값으로 대체됩니다.
  • *.md전역 패턴이므로 일치하는 파일 목록으로 대체됩니다. 일치하는 항목이 없으면 패턴은 변경되지 않습니다.

이로써 실행할 명령을 결정하는 데 필요한 작업이 완료되었습니다.

  • 현재 디렉토리에 일치하는 파일이 없으면 지정된 *.md매개변수를 사용하여 다음 명령이 실행됩니다: find, ., -name, *.md, -exec, ln, -vs, ., /Users/me/OfflineFolder.;
  • 일치하는 항목 이 *.md있는 경우 명령은 , , , , , …입니다( 하위 디렉터리에서 호출된 모든 파일은 누락됩니다 ).bar.mdfoo.mdfind.-namefoo.md-execfindsomething-other-than-foo.md
  • *.md일치하는 bar.md경우 명령 foo.mdfind, ., -name, bar.md, foo.md, -exec, …입니다( find구문 오류에 대해 불평합니다).

dirname검색 결과를 수행하려고 합니다 . 즉, find실행하려면 지침이 필요합니다 dirname. 이를 수행할 수 있지만 find에는 출력을 수집하여 dirname인수로 전달하는 메커니즘이 없습니다 ln. 이를 위해서는 명령 대체인 셸과 같은 도구가 필요합니다.

따라서 전략은 다음과 같습니다. find에게 쉘을 호출하라고 지시하고 해당 쉘에 ln및와 관련된 명령을 실행하라고 지시합니다 dirname. 인용에 주의를 기울여야 합니다. 특수 문자가 셸에서 해석되지 않도록 셸 명령을 작은따옴표로 묶습니다. 또한 패턴이 셸에 의해 확장되지 않고 셸로 -name전달되도록 패턴을 따옴표로 묶습니다 .find

find . -name '*.md' -exec sh -c 'ln -vs …' \;

다음 단계는 완료입니다 . 셸 명령 내에서 사용하지 마세요 {}. 이렇게 하면 파일 이름이 셸 조각으로 처리되며 모든 특수 문자는 내부 셸에서 구문 분석됩니다. 대신, 주어진 파일 이름이 find쉘 스크립트에 인수로 전달됩니다. 그 다음의 첫 번째 인수는 셸 인스턴스의 이름( )이지만 원하는 목적으로 사용할 수 있습니다. 후속 인수는 위치 인수(, , ...)입니다.sh -c CODE$0$1$2

find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$1"' {} "$OUTPUT_DIR" \;

$OUTPUT_DIR스크립트에 매개변수로 전달했습니다 . 값에 셸 특수 문자가 포함되어 있지 않기 때문에 여기서는 중요하지 않지만, 예를 들어 누군가가 공백을 포함하도록 경로를 언제 변경할 수 있는지 알 수 없으므로 이는 좋은 습관입니다. 또 다른 가능성은 환경을 통과하는 것입니다.

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$OUTPUT_DIR"' {} \;

dirname대신 텍스트 대체를 사용할 수 있습니다. 마지막 슬래시 뒤의 모든 내용을 제거하세요. 디렉토리 부분이 없는 특별한 경우에 대해 걱정할 필요가 없습니다. 전달된 filename에서는 그런 일이 발생하지 않기 때문입니다 find.

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "${0%/*}" "$OUTPUT_DIR"' {} \;

+양식을 사용하여 -exec작업 속도를 높일 수 있습니다. 나는 _as를 전달하고 후속 매개변수는 $0루프가 반복되는 파일 이름입니다.for

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'for x; do ln -vs "${x%/*}" "$OUTPUT_DIR"; done' _ {} +

관련 정보