패턴을 기반으로 파일 이름을 바꾸는 데 사용하고 싶은 간단한 스크립트를 만들었습니다.
스크립트는 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
예기치 않게 해석되는 것을 방지하기 위해 전달됩니다 .