파일의 첨부 경로를 전체 경로로 바꾸는 Bash 스크립트

파일의 첨부 경로를 전체 경로로 바꾸는 Bash 스크립트

~/app/Markdown 리더 간의 상호 운용성을 위해 전체 상대 경로가 삽입 되도록 리팩토링하고 싶은 Markdown 파일에 파일 이름과 상대 경로가 혼합되어 있습니다 .

~/app/내 파일에는 첨부 파일을 참조하는 파일이 있습니다.~/app/file1.md~/app/attachments/

예: file1.md다음을 포함합니다:

Here is an image:
![[img1.png]]

Here is another image:
![[attachments/img2.png]]

(두 사진 모두 위치가 있습니다 ~/app/attachments/)

![[]]와 사이에서 참조를 얻을 수 있습니다 .

sed -n 's/!\[\[\(.*\.png\)]]/\1/gp'

find하지만 이것을 인라인을 전체 상대 경로로 바꾸는 명령과 결합하려면 어떻게 해야 합니까 ? - 아래가 아닌 이미지가 있는 곳 어디에서나 작동하고 싶습니다.attachments/

결과 file1.md는 다음과 같습니다:

Here is an image:
![[attachments/img1.png]]

Here is another image:
![[attachments/img2.png]]

답변1

다음은 이를 수행하는 Perl 스크립트의 세 가지 버전입니다. 세 가지 모두 첫 번째 매개변수는 검색할 디렉터리(예: ./app또는 ./)여야 합니다. 나머지 매개변수는 수정할 Markdown 파일의 이름입니다(예: ./app/file1.md또는 ./app/*.md).

둘 다 검색 파일에만 기록 되지만 .png사용되는 정규식과 전역 변수를 변경하여 쉽게 변경할 수 있습니다.

#!참고: 세 스크립트 모두 표준 출력으로 인쇄하는 대신 스크립트가 Markdown 파일을 수정하도록 하려면 첫 번째 줄을 제거하십시오. 첫 번째#! 요구 사항을 충족하는지 확인하기 위한 테스트에 사용됩니다. 두 번째는 실제로 마크다운 파일을 수정합니다(그리고 원본 파일을 .bak에 복사합니다. -i.bak백업 복사본을 만들지 않으려면 변경하면 됩니다). 이 옵션의 작동 방식에 대한 세부정보를 -i보고 man perlrun검색하세요 .-i

또한 참고하시기 바랍니다파일::기본 이름그리고파일::찾기사용되는 모듈은 Perl에 포함된 Perl 코어 라이브러리 모듈입니다. File::Basename기본적으로 명령의 작업을 수행 basename하고 File::Find명령과 같이 반복적으로 디렉터리를 검색합니다 find.

왜 sh나 bash 대신 Perl을 사용하나요? 쉘은 텍스트나 데이터 처리에 있어서 끔찍한 언어이기 때문입니다. 바라보다쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?몇 가지 이유. Shell의 임무는 조정하는 것입니다.기타 프로그램데이터 처리 자체가 아닌 데이터 처리 작업을 수행합니다. 데이터 처리를 위해 셸을 사용하는 것은 드라이버가 필요할 때 삽을 사용하고, 국자가 필요할 때 포크를 사용하는 것과 같습니다.

세 가지 버전 모두 다음 파일 및 디렉터리 구조를 사용하여 테스트되었습니다.

app/attachments/img1.png
app/attachments/img2.png
app/attachments/more/img5.png
app/file1.md
app/file2.md
app/file3.md
app/img4.png
app/other/img3.png
첫 번째 버전

첫 번째 버전은 첨부 파일이 ./app 또는 ./app/attachments에서만 찾을 수 있는 경우 유용합니다.

첨부 파일이 표시로 지정된 위치에서 발견되면 ![[filename]]그대로 유지됩니다. 찾을 수 없는 경우 스크립트는 먼저 최상위 디렉터리를 찾은 다음 Attachments/ 하위 디렉터리를 찾습니다.

$ cat fix-paths1.pl 
#!/usr/bin/perl -p
#!/usr/bin/perl -p -i.bak

BEGIN { $dir = shift };

use File::Basename;

if (/!\[\[([^]]*\.png)\]\]/i) {
  $file = $1;
  next if -f $file1;
  $bn = fileparse($file);

  if (-f "$dir/$bn") {
    s/$file/$bn/
  } elsif (-f "$dir/attachments/$bn") {
    s/$file/attachments\/$bn/
  } else {
    print STDERR "WARNING: Attachment '$file' does not exist. $ARGV:$.\n"
  };
}

실행 예 - file1.md 귀하의 질문과 동일합니다.

$ ./fix-paths1.pl ./app/ app/file1.md 
Here is an image:
![[attachments/img1.png]]

Here is another image:
![[attachments/img2.png]]
두번째 버전

두 번째 버전은 파일이 ./app/의 직접 하위 디렉터리에서 찾을 수 있는 경우 유용합니다. 즉, app/attachments/는 있지만 app/attachments/more/는 찾을 수 없습니다.

이는 Perl의 기능을 사용하여 지정된 디렉토리( ) 및 모든 직계 하위 디렉토리에 glob파일 배열을 만듭니다 . 이 배열은 일치하는 모든 파일에 대한 캐시 역할을 합니다. 디렉토리 검색은 상당히 "비용이 많이 드는" 작업이기 때문입니다. 확실히 루프에서 반복적으로 수행하고 싶지 않은 작업입니다..png./app/

$ cat fix-paths2.pl
#!/usr/bin/perl -p
#!/usr/bin/perl -p -i.bak

use File::Basename;

BEGIN {
  $dir = shift;
  $dir =~ s:/+$::;

  @png = glob("$dir/*.png");
  push @png, glob("$dir/*/*.png");
  @png = map { s:^$dir/:: ? $_ : $_ } @png;
};

if (/!\[\[([^]]*\.png)\]\]/i) {
  $file = $1;
  next if -f $file1;

  $bn = fileparse($file);

  ($found) = grep { m:(^|/)$bn$: } @png;

  if ($found) {
    s/$file/$found/;
  } else {
    print STDERR "WARNING: Attachment '$file' does not exist. $ARGV:$.\n"
  };
}

실행 예시:

$ cat app/file2.md 
Here is an image:
![[img1.png]]

Here is another image:
![[attachments/img2.png]]

and another:
![[img3.png]]



$ ./fix-paths2.pl ./app/ ./app/file2.md 
Here is an image:
![[attachments/img1.png]]

Here is another image:
![[attachments/img2.png]]

and another:
![[other/img3.png]]

이 릴리스에서는 img1.png 및 img3.png의 경로를 찾아 수정합니다.

세 번째 버전

세 번째 버전은 첨부 파일이 ./app/디렉토리 트리의 깊이에 관계없이 의 하위 디렉토리에서 찾을 수 있는 경우 유용합니다. 이 버전과 두 번째 버전의 유일한 차이점은 배열을 채우는 방식입니다 @png. 두 번째 버전에서는 이 glob()기능을 사용하고 세 번째 버전에서는 File::Find.

검색 결과를 캐싱하는 데 사용되는 @png 배열은 여기서 실제로 그 가치를 보여줍니다. 재귀 디렉터리 검색은 "간단한" 전역 검색보다 비용이 더 많이 드는 작업입니다.

$ cat fix-paths3.pl
#!/usr/bin/perl -p
#!/usr/bin/perl -p -i.bak

use File::Basename;
use File::Find;

BEGIN {
  $dir = shift;
  $dir =~ s:/+$::;

  sub wanted {
    if (m/\.png$/) {
      ($f = $File::Find::name) =~ s:^$dir/::;
      push @png, "$f";
    };
  };

  find(\&wanted, $dir);
};

if (/!\[\[([^]]*\.png)\]\]/i) {
  $file = $1;
  next if -f $file1;

  $bn = fileparse($file);

  ($found) = grep { m:(^|/)$bn$: } @png;

  if ($found) {
    s/$file/$found/;
  } else {
    print STDERR "WARNING: Attachment '$file' does not exist. $ARGV:$.\n"
  };
}

실행 예시:

$ cat app/file3.md 
Here is an image:
![[img1.png]]

Here is another image:
![[attachments/img2.png]]

and another:
![[img3.png]]

and another:
![[attachments/img4.png]]

and another:
![[other/img5.png]]



$ ./fix-paths3.pl ./app/ ./app/file3.md 
Here is an image:
![[attachments/img1.png]]

Here is another image:
![[attachments/img2.png]]

and another:
![[other/img3.png]]

and another:
![[img4.png]]

and another:

![[attachments/more/img5.png]]

이 버전에서는 file3.md 파일이 다음 위치에 있다고 나와 img5.png있지만attachments/more/other/

실수

  1. $file첨부 파일 이름에 초과 공백이 있는지 여부와 Markdown 인터프리터가 초과 공백을 얼마나 엄격하게 처리하는지에 따라 선행 및 후행 공백을 제거하는 것이 좋습니다 . 뒤에 다음 줄을 추가합니다 $file = $1;.

    $file =~ s/^\s*|\s*$//g;
    
  2. 마크다운 파일에 표시된 위치에서 .png 파일을 찾을 수 없는 경우 두 번째 및 세 번째 버전이 반환됩니다.첫 번째동일한 이름을 가진 파일이 여러 개 있더라도 파일을 일치시킵니다(예, 이는 실제 버그라기보다는 디자인 결정에 가깝습니다. 저는 이 방식으로 작성하기로 결정했습니다). 때로는 예상한 파일이 아닐 수도 있습니다. 이는 다음의 자연스러운 결과입니다.지고 규칙.

    이것은 일치하는 수를 세어 "수정"할 수 있으며(힌트: Perl의 내장 grep함수는 배열을 반환합니다. 위의 스크립트는 첫 번째 결과를 제외한 모든 결과를 삭제합니다. 해당 $found변수는 배열 변수로 대체될 수 있습니다 @found) 오류가 있으면 인쇄합니다. 더 많거나 특정 디렉터리의 첨부 파일을 다른 디렉터리보다 선호하는(또는 이전 파일보다 최신 파일을 선호하거나 새 파일보다 오래된 파일을 선호하는 등) 경험적 방법이 있습니다. 실제 해결책은 모호함을 피하기 위해 입력 Markdown 파일을 편집하는 것입니다.

    perldoc -f grepPerl 함수에 대한 자세한 내용은 참고자료를 참조하세요 grep.

답변2

한 줄에 하나의 파일 링크만 있고 한 줄씩 수행한다고 가정하면 sed괜찮을 것입니다. 그래서 우리는 다음과 같은 것을 가지고 있습니다:

file=$(sed -n 's/!\[\[\(.*\.png\)]]/\1/gp')
name=$(basename "$file")
fullFile=$(find . -name "$name" -print -quit)

-quit여기서는 첫 번째 일치 항목이 발견되자마자 검색이 중지되도록 사용합니다 . 이것이 당신이 올바른 방향으로 갈 수 있기를 바랍니다.

관련 정보