선택한 디렉토리에 있는 여러 파일의 문자열을 바꾸는 쉘 스크립트

선택한 디렉토리에 있는 여러 파일의 문자열을 바꾸는 쉘 스크립트

단일 디렉터리의 경로를 사용하고 해당 디렉터리에 있는 모든 파일의 검색 문자열을 바꾸는 다음 스크립트를 만들었습니다. 외부 입력 파일에 나열된 여러 디렉터리의 문자열을 검색하고 바꿀 수 있도록 이 스크립트를 향상시키고 싶습니다.

외부 입력 파일 내용:

/var/start/system1/dir1
/var/start/system2/dir2
/var/start/system3/dir3
/var/start/system4/dir4

하나의 디렉토리가 있는 스크립트:

filepath="/var/start/system/dir1"
searchstring="test"
replacestring="test01"

i=0; 

for file in $(grep -l -R $searchstring $filepath)
do
  cp $file $file.bak
  sed -e "s/$searchstring/$replacestring/ig" $file > tempfile.tmp
  mv tempfile.tmp $file

  let i++;

  echo "Modified: " $file
done

답변1

sed -i첫째, GNU sedsed -i ''FreeBSD(내부 교체) 와 함께 사용 하면 tmpfile 댄스를 피할 수 있습니다 .

grep -R명령줄에서 여러 경로를 사용할 수 있으므로 경로에 공백이 포함되어 있지 않다고 확신하는 경우 $(grep -l -Re "$searchstring" "$filepath")로 바꿀 수 있습니다 $(grep -l -R "$searchstring" $(cat path_list)).

경로에 공백, 탭 또는 와일드카드 문자가 포함되어 있으면 이 작업은 실패하지만 원래 경로도 마찬가지입니다.

보다 강력한 접근 방식은 findsed를 사용하고 이를 모든 파일에 적용하여 일치하지 않는 파일을 수정하지 않을 것이라고 믿습니다( bash여기서 쉘을 가정).

# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list

# Run sed on everything
find "${paths[@]}" \
  -exec sed -i -r -e "s/$searchstring/$replacestring/ig" '{}' ';'

그러나 이는 수정 중인 파일에 대한 피드백을 제공하지 않습니다.

더 긴 버전은 피드백을 제공합니다.

# Read newline-separated path list into array from file 'path_list'
IFS=$'\n' read -d '' -r -a paths path_list

grep -l -R "$searchstring" "${paths[@]}" | while IFS= read -r file; do
  sed -r -i -e "s/$searchstring/$replacestring/ig" "$file"
  echo "Modified: $file"
done

답변2

GNU 도구 사용

< dir.list xargs -rd '\n' grep -rlZ -- "$searchstring" |
  xargs -r0 sed -i -e "s/$searchstring/$replacestring/ig" --

(변수를 인용하는 것을 잊지 마세요. 인용되지 않은 변수는 분할+글로브 연산자입니다)

답변3

이것은 내가 생각할 수 있는 가장 이식성이 뛰어난 방법입니다.최대휴대용 /dev/fd/0..dot 그것이 없으면 단일 파일을 사용할 수 있습니다. 어쨌든, 이는 제가 일전에 작성한 쉘 함수에 주로 의존합니다.

_sed_cesc_qt() { 
    sed -n ':n;\|^'"$1"'|!{H;$!{n;bn}};{$l;x;l}' |
    sed -n '\|^'"$1"'|{:n;\|[$]$|!{
            N;s|.\n||;bn};s|||
            \|\([^\\]\)\\\([0-9]\)|{
            s||\1\\0\2|g;}'"
            s|'"'|&"&"&|g;'"s|.*|'&'|p}"

}

먼저 어떻게 작동하는지 보여주고, 그 다음에는 어떻게 하는지 설명하겠습니다. 따라서 테스트 저장소를 생성하겠습니다.

printf 'f=%d
    echo "$f" >./"$f"
    echo "$f" >./"$f\n$f"
    echo "$f" >./"$f\n$f\n$f"
' $(seq 10) | . /dev/fd/0

그러면 각 파일에 포함된 숫자 1-10의 이름을 딴 여러 파일이 생성됩니다.

ls -qm 
1, 1?1, 1?1?1, 10, 10?10, 10?10?10, 2, 2?2, 2?2?2, 3, 3?3, 3?3?3, 4, 4?4, 4?4?4, 5, 5?5, 5?5?5, 6, 6?6, 6?6?6, 7,
7?7, 7?7?7, 8, 8?8, 8?8?8, 9, 9?9, 9?9?9

이것은 내 테스트 디렉터리에 있는 쉼표로 구분된 파일 목록이며, 각 파일은 ?개행을 나타냅니다.

cat ./1*

1
1
1
10
10
10

각 파일에는 하나의 숫자만 포함됩니다.

이제 다음을 교체하겠습니다 grep.

find ././ \! -type d -exec \
        grep -l '[02468]$' \{\} + |
_sed_cesc_qt '\./\./' | 
sed 's|.|\\&|g' |
xargs printf 'f=%b
        sed "/[02468]\\$/s//CHANGED/" <<-SED >"$f"
        $(cat <"$f")
        SED\n' | 
. /dev/fd/0

이제 내가...

cat ./1*

1
1
1
1CHANGED
1CHANGED
1CHANGED

모든 [2468]파일은 유사합니다 CHANGED. 또한 재귀적으로 작동합니다. 좋아요, 이제 방법을 설명하겠습니다.

먼저 이 기능은 다음과 같습니다.

  1. :n확장 태그로 시작
  2. \|주소 |매개변수 $1- 토큰
  3. 현재 행이 !일치하지 않는 경우{
    • H이전 버퍼 에 추가
    • !현재 줄이 마지막 $줄 이 아닌 경우{
    • 확장된 행으로 n현재 행 덮어쓰기
    • b랜치 백 :n확장 태그
    • }}
  4. 그렇지 않고 현재 줄이 $마지막 줄 이라면 l패턴 공간을 살펴보세요.
  5. 그렇지 않으면 e는 x홀드 및 패턴 버퍼의 내용을 변경하고...
  6. l패턴 공간을 명확하게 살펴보세요

이것이 첫 번째 sed진술입니다. 이것이 바로 핵심입니다. 우리는 p패턴 공간을 전혀 인쇄 하지 않고 단지 l보기만 합니다. lPOSIX가 함수를 정의하는 방법은 다음과 같습니다 .

[2주소]l (이 편지는 ell입니다.)시각적으로 명확한 형식으로 패턴 공간을 표준 출력에 기록합니다. IEEE Std 1003.1-2001 기본 정의 볼륨 표 5-1, 이스케이프 시퀀스 및 관련 작업에 나열된 문자는 ( '\\', '\a', '\b', '\f', '\r', '\t', '\v' )해당 이스케이프 시퀀스로 작성되어야 하며 이 표의 내용은 '\n' 적용되지 않습니다. 이 표에 없는 인쇄할 수 없는 문자는세 자리\문자에 있는 각 바이트의 8진수(앞에 백슬래시가 옴)입니다(가장 중요한 바이트부터). 긴 줄은 접어야 하며, 접는 지점은 \백슬래시 뒤에 슬래시 를 써서 표시됩니다 \n. 접는 길이는 지정되지 않지만 출력 장치에 맞아야 합니다. 각 줄의 끝은 으로 표시되어야 합니다 '$'.

그렇다면 다음과 같이 하세요.

printf '\e%s10\n10\n10' '\' | sed -n 'N;N;l'

나는 얻다:

\033\\10\n10\n10$

그것은 거의 완벽한 탈출이었다 printf. 단지 8진수에 0을 추가하고 후행을 제거하므로 $다음 sed명령문에서는 이를 지웁니다.

동일한 세부 사항을 다루지는 않겠지만 기본적으로 다음 sed설명은 다음과 같습니다.

  1. 행이 $1태그로 시작하는 경우...
  2. N현재 줄이 끝날 때까지 추가 줄을 끌어옵니다.$
  3. \위 작업을 수행해야 하는 경우 후행 백슬래시와 줄 \n바꿈 문자가 제거됩니다 .
  4. 그런 다음 후행을 제거합니다.$
  5. \백슬래시 뒤에 숫자가 오고 다른 백슬래시가 앞에 오지 않는 것을 찾아 \0을 삽입하세요.
  6. 작은따옴표를 검색 '하고 큰따옴표를 사용하세요.
  7. '마지막으로 전체 문자열을 작은따옴표로 묶습니다.

이제 이 작업을 수행하면 다음과 같습니다.

printf %s\\n ././1* |
_sed_cesc_qt '\./\./'

나는 얻다:

'././1'
'././1\n1'
'././1\n1\n1'
'././10'
'././10\n10'
'././10\n10\n10'

나머지는 쉽습니다. 이는 문자열이 구문 분석된다는 사실에 따라 다르지만 출력 ././에서는 각 경로 이름의 시작 부분에만 표시되므로 표시자가 됩니다.find/grep$1

정규 표현식이 포함된 파일의 파일 이름을 출력하도록 -exec grep시작 find하고 지정했습니다 .-l

함수를 호출하고 출력을 얻습니다.

그런 다음 \백슬래시로 출력의 모든 문자를 이스케이프 처리합니다 xargs.

파일 printf에 스크립트를 작성합니다 . 소스 를 로 지정 합니다. 변수를 현재 매개변수(내 경로 이름)로 정의하고 해당 매개변수를 에 제공되는 heredocument 에 전달 하고 소스 파일에 다시 작성합니다.|pipe.dot/dev/fd/0fcat$f<<sedsed

여기에는 임시 파일이 포함될 수 있으며 쉘에 따라 다릅니다. 각 여기 문서에 대한 임시 파일을 bash작성 zsh하지만 정리도 수행합니다. dash반면 이곳의 문서만 익명으로 작성됩니다 |pipe.

그러나 파일을 쓰기 전에 파일을 완전히 읽어야 하는 것이 중요합니다. 이것이 문서 및 명령 대체가 작동하는 방식입니다.

관련 정보