패턴 및 교체 목록을 사용하여 반복적으로 파일 이름 바꾸기

패턴 및 교체 목록을 사용하여 반복적으로 파일 이름 바꾸기

다음과 같은 파일 구조가 있습니다.

  • 일부 디렉토리
    • 일부 file.txt
    • 여기에 또 다른 파일이 있습니다.log
    • 또 다른 파일.mp3
  • 다른 디렉토리
    • 다른 file.txt로
  • 루트 level.txt의 파일
  • 루트 level.ext의 다른 파일

지금 내가 원하는 것은 다른 파일을 입력으로 사용하여 일부 유형의 패턴/대체 쌍을 포함하는 작은 스크립트를 실행하여 해당 파일에 따라 이 파일의 이름을 재귀적으로 바꾸는 것입니다. 따라서 모든 "another"(대소문자 구분 안 함)는 "foo"로 대체되거나 모든 "some"은 "bar"로 대체됩니다.

나는 이미 파일을 반복하고 해당 입력 파일을 읽으면서 많은 것을 시도했지만 원하는 대로 작동하지 않았고 마침내 실수로 테스트 스크립트를 덮어쓰게 되었습니다. 그런데 ls, while, sed을 많이 mv사용하고 있었습니다.

내가 스스로 해결할 수 없었던 두 가지 문제는 파일 이름의 공백을 처리하는 방법과 이전 패턴 일치에서 이미 이름이 변경된 파일을 처리하지 않는 방법이었습니다.

어쩌면 당신이 나에게 올바른 방향을 알려줄 수 있을까요?

답변1

TOP="`pwd -P`" \
find . -type d -exec sh -c '
   for d
   do
      cd "$d" && \
         find . ! -name . -prune -type f -exec sh -c '\''
            while IFS=\; read -r pat repl
            do
               rename "s/$pat/$repl/g" "$@"
               N=$#
               for unmoved
               do
                  if [ -f "$unmoved" ]
                  then
                     set X ${1+"$@"} "$unmoved"
                     shift
                  fi
               done
               shift "$N"
               case $# in 0 ) break ;; esac
            done < patterns.csv
         '\'' x \{\} +
      cd "$TOP"
   done
' x {} +
  • find넷 디렉토리만 설정 하고 sh단숨에 내려놓으세요. 이렇게 하면 호출 횟수가 최소화됩니다 sh.
  • find이러한 각 디렉토리에 regular깊이 레벨 1의 넷 파일을 설정하고 sh꿀꺽 꿀꺽 꿀꺽 먹습니다. 이렇게 하면 rename유틸리티가 호출되는 횟수가 최소화됩니다 .
  • while다양한 쌍을 읽어서 pattern <-> replacement모든 파일에 적용하도록 루프 를 설정합니다 regular.
  • 그 과정에서 rename-ing프로세스 후에도 파일이 아직 남아 있는지 여부를 기록해 둡니다 rename. 파일이 여전히 존재한다는 것을 발견하면 어떤 이유로 이름을 바꿀 수 없으므로 다음 pat/repl반복에서 시도된다는 의미입니다. OTOH, 파일 이름이 성공적으로 변경된 경우 pat/repl명령줄 인수 목록에서 파일을 가져와서 이 파일에 다음 반복을 적용하지 않습니다.

답변2

rPairs="/tmp/rename_pairs" \
find . -type f -exec sh -c '
   while read -r old new; do
      rename "s/$old/$new/i" "$@"
   done < "$rPairs"
' x {} +

이름 바꾸기 쌍 파일에 비ASCII 문자가 없고 이 파일도 검색 경로에서 멀리 떨어져 있다고 가정합니다.

답변3

Rakesh Sharma의 답변 이후 저는 좀 더 실험하고 잠을 자고 나서 올바른 방향으로 나아갔습니다.

마침내 나는 다음 스크립트를 생각해 냈습니다.

#!/bin/bash


while IFS=";" read pattern replacement
do
  if [[ ! -z $pattern ]]
  then
    echo "Checking files for pattern '$pattern'."

    find ./files -name "*$pattern*" -type f | while read fpath
    do
      fname=$(basename "$fpath")
      dname=$(dirname "$fpath")

      echo "  Found file '$fname' in directory '$dname'. Renaming to '${fname/$pattern/$replacement}'."
      mv -- "$fpath" "$dname/${fname/$pattern/$replacement}"
    done
  fi
done < patterns.csv

파일을 읽고 및 변수를 pattern.csv채우는 행을 반복합니다. 두 번째 단계에서는 디렉토리 내의 현재 패턴과 일치하는 모든 파일을 찾습니다. 두 번째 패턴이 일치할 때 파일 이름을 다시 바꾸려고 시도하지 않으려면 이 작업을 수행해야 합니다. 왜냐하면 실패할 수 있기 때문입니다. 마지막으로 쉘 매개변수 대체를 사용하여 파일 이름을 포함하는 디렉토리가 아닌 파일 자체의 이름만 바꿉니다.$pattern$replacement./files

작동하지 않는 것은 대소 문자를 구분하지 않고 일치 항목을 바꾸는 것입니다. 그러나 나는 그걸로 살아갈 수 있습니다.

답변4

명심해야 할 중요한 점은 디렉토리 트리를 탐색하는 것은 느린 프로세스이므로 한 번만 수행된다는 것입니다. 우리가 하는 일은 먼저 find트리에 있는 디렉토리만 살펴보는 것입니다. 그리고 각 디렉토리에 대해 그 아래에 있는 모든 항목을 찾습니다 regular files(여기서는 재귀가 없습니다). 그런 다음 이러한 파일 이름에 이름 바꾸기 변환을 적용하는 동시에 성공 여부를 기록해 둡니다. 성공하면 while 루프를 종료하여 다음 patt/repl이 이 파일에 적용되지 않도록 합니다.

tempd="`mktemp -d`" \
find . -type d -exec sh -c '
   cd "$1" && \
   for f in ./*
   do
      [ -f "$f" ] || continue
      while IFS=\; read -r patt repl
      do
         case $f in
            ./*"$patt"* )
               rename -v "s/$patt/$repl/g" "$f" 2>&1 | tee "$tempd/$f"
               case $(< "$tempf/$f") in "$f renamed "* ) break ;; esac ;;
         esac
      done < /tmp/patterns.csv
   done
' {} {} \;

관련 정보