스크립트에서 마지막 항목만 일치시키려고 하면 Sed가 $address 문자를 존중하지 않습니다.

스크립트에서 마지막 항목만 일치시키려고 하면 Sed가 $address 문자를 존중하지 않습니다.

패턴을 기반으로 파일 이름을 바꾸는 데 사용하고 싶은 간단한 스크립트를 만들었습니다.

스크립트는 find 및 sed를 사용하여 작업을 수행합니다.

기본적으로 모든 것이 잘 작동합니다. 단, $를 사용하여 sed에게 마지막 항목만 일치하도록 지시하면 아무것도 일치하지 않으므로 이름 바꾸기를 수행하지 않습니다. $를 제거하면 이름 바꾸기가 작동하지만 모든 항목의 이름이 바뀌어 내가 원하는 것과는 다르게 특히 마지막 항목을 대상으로 지정하고 싶습니다.

나는 문서, YouTube 튜토리얼, 스택 오버플로, 스택 교환 등을 검색했지만 이러한 것 중 어느 것도 작동하지 않거나 내 문제와 관련이 없다는 것을 발견했습니다. 특히 $ 주소 문자가 작동하지 않는 경우 더욱 그렇습니다.

$char를 사용하거나 사용하지 않고 sed를 사용하는 방법을 보여주는 아래 스크립트를 테스트하여 제가 겪고 있는 문제를 확인할 수 있습니다. 로직은 searchReplace()와 searchReplaceLastMatchOnly()라는 두 가지 함수로 나뉩니다. searchReplaceLastMatchOnly() 함수는 내가 작동해야 하는 함수이지만 어떤 것과도 일치하지 않습니다.

내 테스트 디렉터리 구조는 다음과 같습니다.

./bar
./bar/baz
./bar/foobar.txt 
./bar/baz/foobar.txt

다음과 같이 테스트 디렉터리에서 스크립트를 실행합니다.

./script.sh -d "." -s "bar" -r "Bar" -p "*.txt" -e "txt"

변경해야 할 사항:

./bar/foobar.txt to ./bar/fooBar.txt

그리고:

./bar/baz/foobar.txt to ./bar/baz/fooBar.txt

- - 실제 결과- -

사용 searchReplace:

Renaming ./bar/baz/foobar.txt to ./Bar/baz/fooBar.txt

Renaming ./bar/foobar.txt to ./Bar/fooBar.txt

사용 searchReplaceLastMatchOnly:

Renaming ./bar/baz/foobar.txt to ./bar/baz/foobar.txt

Renaming ./bar/foobar.txt to ./bar/foobar.txt

전체 스크립트는 다음과 같습니다.

# Search and replace using sed.
# $1 target The string to process
# $2 The string to serach for
# $3 The string that will repalce the search string
# Usage Example:
# result=searchReplace "targetToSearch" "Search" "Replace"
# printf "%s" "$result" # should output "targetToReplace"
function searchReplace() {
  # spaces are set as defaults
  target=${1:- }
  search=${2:- }
  replace=${3:- }
  result="$(printf "%s" "$target" | sed "s/${search}/${replace}/g")"
  printf "%s" "$result"
}

# Prints via printf with wrapping newline(\n) chars.
# Note: If no arguments are supplied, pretty print
#       will simpy print a default string followed by
#       a newline character (\n).
# Usage Exmple:
# txt="Text"
# prettyPrint "$txt"
function prettyPrint() {
  # Set default text to print in case no arguments were passed
  # (at the moment this is an empty string)
  text=${1:-}
  [[ -z $text ]] && printf "\n" || printf "\n%s\n" "$text"
  #
}

# Get option values
while getopts "d:p:s:r:e:" opt; do
  case $opt in
  d) dirPath="$OPTARG" ;;
  p) pattern="$OPTARG" ;;
  s) search="$OPTARG" ;;
  r) replace="$OPTARG" ;;
  e) fileExt="$OPTARG" ;;
  *)
    prettyPrint "Error: Invalid flag $opt"
    exit 1
    ;;
  esac
done

# Defaults #
dirPath=${dirPath:-.}
pattern=${pattern:-*}
search=${search:- }
replace=${replace:- }
fileExt=${fileExt:-txt}

prettyPrint "Using searchReplace:"
find "$dirPath" -type f -name "$pattern" | while IFS= read -r original; do
  modified="$(searchReplace "$original" "$search" "$replace")"
  prettyPrint "Renaming $original to $modified"
  #mv "$original" "$modified" | This is the goal...no using till renaming is working.
done

# The dev directory is structured as follows:
# .
# ./bar/fooBar.txt
# ./bar/baz/fooBar.txt
#
# This script when run as follows:
#
# ./script.sh -d "." -s "bar" -r "Bar" -p "*.txt" -e "txt"
#
# Should rename:
# ./bar/foobar.txt to ./bar/fooBar.txt
#
# and also rename:
# ./bar/baz/foobar.txt to ./bar/baz/fooBar.txt
#
# However when I run this script it renames as follows:
#
# ./bar/baz/foobar.txt to ./Bar/baz/fooBar.txt
#
# ./bar/foobar.txt to ./Bar/fooBar.txt
#
# As you can see the ./bar directory is also renamed to Bar, i just want the last
# occurence of bar to be renamed to Bar.
#
# I tried modifying the sed command in the searchReplace() function
# from sed "s/${search}/${replace}/g" to sed "s/${search}$/${replace}/g"
# as i read that the $ should tell sed to match only the last occurence,
# but it doesn't work.
# Below is the modified searchReplace() function that uses the $
#
function searchReplaceLastMatchOnly() {
  # spaces are set as defaults
  target=${1:- }
  search=${2:- }
  replace=${3:- }
  result="$(printf "%s" "$target" | sed "s/${search}$/${replace}/g")"
  printf "%s" "$result"
}

prettyPrint "Using searchReplaceLastMatchOnly:"
# here it is running
find "$dirPath" -type f -name "$pattern" | while IFS= read -r original; do
  modified="$(searchReplaceLastMatchOnly "$original" "$search" "$replace")"
  prettyPrint "Renaming $original to $modified"
  #mv "$original" "$modified" | This is the goal...no using till renaming is working.
done

답변1

마지막 항목만 바꾸려면 다음을 바꾸십시오.

result="$(printf "%s" "$target" | sed "s/${search}$/${replace}/g")

그리고

result="$(printf "%s" "$target" | sed "s/\(.*\)${search}/\1${replace}/")"

$마지막 검색 패턴이 아닌 줄의 끝 과 일치하며 g(최대) 하나의 대체 항목이 있으므로 수정자가 필요하지 않습니다.

탐욕적 \(.*\)이며 sed마지막 패턴이 발견될 $search때까지 모든 것을 일치시킵니다 . 이 부품을 제거하고 싶지 않기 때문에 \1교체품에 포함시켜야 합니다 .

노트:

  • #!/bin/bash스크립트의 첫 번째 줄에 shebang이 없습니다.
  • -e아직 구현되지 않았습니다.
  • 한 번만 사용되는 변수가 많이 있습니다. 이를 제거하면 코드가 더 명확해집니다. 예를 들어 searchReplaceLastMatchOnly()함수는 다음과 같이 단순화될 수 있습니다 .

    function searchReplaceLastMatchOnly() {
      printf "%s" "${1:- }" | sed "s/\(.*\)${2:- }/\1${3:- }/"
    }
    

답변2

앵커 $:

문자를 사용하지 않고 문자열의 끝과 일치합니다. 여러 줄 모드를 사용하는 경우 개행 문자 바로 앞의 위치와도 일치합니다.

이는 문자열이 실제로 불일치 txt$가 아니라 다음 으로 끝난다는 점에서 "마지막 발생"과 다릅니다 .bar$

마지막 항목만 일치시키는 한 가지 방법은 역방향 문자열을 사용 rev하고 첫 번째 항목만 바꾸는 것입니다(물론 교체도 역순으로 수행해야 합니다!).

function searchReplaceLastMatchOnly() {
  # spaces are set as defaults
  local target=$(rev <<<"${1:- }")
  local search=$(rev <<<"${2:- }")
  local replace=$(rev <<<"${3:- }")
  local esearch=$(printf '%s\n' "$search" | sed 's:[][\/.^$*]:\\&:g')
  local ereplace=$(printf '%s\n' "$replace" | sed 's:[\/&]:\\&:g;$!s/$/\\/')
  result="$(printf "%s" "$target" | sed "s/${esearch}/${ereplace}/" | rev)"
  printf "%s" "$result"
}

참고: g연산자가 sed명령에서 제거되었으므로첫 번째교체되는 일이 발생합니다. 게다가 우리는이스케이프 변수sed예기치 않게 해석되는 것을 방지하기 위해 전달됩니다 .

관련 정보