파일에서 한 줄을 변경하는 가장 효율적인 방법

파일에서 한 줄을 변경하는 가장 효율적인 방법

가장 효율적인 방법으로 수백 개의 파일 중 첫 번째 줄을 재귀적으로 변경하고 싶습니다. 내가 하고 싶었던 작업의 예는 #!/bin/bash으로 변경하는 것이었 #!/bin/sh으므로 다음 명령을 생각해냈습니다.

find ./* -type f -exec sed -i '1s/^#!\/bin\/bash/#!\/bin\/sh/' {} \;

그러나 내가 이해한 바로는 이 sed를 수행하려면 전체 파일을 읽고 원본 파일을 교체해야 합니다. 이를 수행하는 더 효율적인 방법이 있습니까?

답변1

네, sed -i파일 전체를 읽고 다시 쓰는데, 줄 길이가 바뀌기 때문에 다른 모든 줄의 위치도 이동하기 때문에 꼭 필요합니다.

...하지만 이 경우 와이어 길이를 실제로 변경할 필요는 없습니다. #!/bin/sh␣␣해시뱅 줄을 두 개의 후행 공백으로 바꿀 수 있습니다 . 운영 체제는 hashbang 줄을 구문 분석할 때 이러한 내용을 제거합니다. (또는 두 개의 줄 바꿈 또는 줄 바꿈 + 파운드 기호를 사용하십시오. 둘 다 쉘이 결국 무시할 추가 줄을 생성합니다.)

우리가 해야 할 일은 파일을 열고 파일을 자르는 대신 처음부터 쓰는 것뿐입니다. 일반적인 리디렉션 에서는 이 작업을 수행 >하지 >>않지만 Bash에서는 읽기-쓰기 리디렉션이 <>작동하는 것 같습니다.

echo '#!/bin/sh  ' 1<> foo.sh

또는 dd다음을 사용하십시오(표준 POSIX 옵션이어야 함).

echo '#!/bin/sh  ' | dd of=foo.sh conv=notrunc

엄밀히 말하면 이 두 가지 모두 줄 끝의 개행 문자를 다시 작성하지만 문제가 되지 않습니다.

물론 위의 내용은 주어진 파일의 시작 부분을 무조건 덮어씁니다. 원본 파일에 올바른 해시뱅이 있는지 확인하는 것을 추가하는 것은 연습으로 남습니다... 어쨌든 프로덕션에서는 이 작업을 수행하지 않을 것입니다. 분명히 라인을 다음으로 변경해야 한다면더 길게하나.

답변2

{} +한 가지 최적화는 대신 사용하는 것입니다 {} \;.

find . -type f -exec sed -i '1s|^#!/bin/bash|#!/bin/sh|' {} +

발견된 각 파일에 대해 sed 프로세스를 호출하는 대신 해당 파일을 단일 sed 프로세스에 대한 인수로 제공합니다.

찾기에 대한 POSIX 사양{} +(굵은 글씨로):

기본 표현식이 <더하기 기호>로 구분되는 경우 기본 표현식은 항상 true로 평가되며 기본 표현식이 평가되는 경로 이름은 컬렉션으로 집계됩니다.유틸리티util_name은 각 집합 경로 이름 집합에 대해 한 번 호출되어야 합니다.

답변3

나는 그것을 할 것이다:

#! /bin/zsh -
LC_ALL=C # work with bytes instead of characters.
shebang_to_replace=$'#!/bin/bash\n'
       new_shebang=$'#!/bin/sh -\n'

length=$#shebang_to_replace

ret=0
for file in **/*(N.L+$((length - 1)));do
  if
    read -u0 -k $length shebang < $file &&
      [[ $shebang = $shebang_to_replace ]]
  then
    print -rn -- $new_shebang 1<> $file || ret=$?
  fi
done
exit $ret

좋다@ilkkachu의 접근 방식, 파일은 정확히 동일한 크기의 문자열로 덮어쓰여집니다. 차이점은 다음과 같습니다.

  • 우리는 숨겨진 파일과 숨겨진 디렉터리( .git예: 하나를 고려)에 있는 파일을 무시합니다. 왜냐하면 이러한 파일을 고려하고 싶지 않을 것이기 때문입니다(사용 중인 파일은 find ./*현재 디렉터리의 숨겨진 파일과 디렉터리를 건너뛰지만 하위 디렉터리의 숨겨진 파일과 디렉터리는 건너뛰지 않습니다). D꼭 필요한 경우에는 glob 한정자를 추가하세요.
  • 교체할 원래 shebang을 담을 만큼 크지 않은 파일을 찾는 데 신경 쓰지 않을 것입니다. ( .equivalent 를 사용 -type f하므로 파일에서 이미 inode 정보를 검색했으므로 거기서 크기를 확인하는 것이 좋습니다.)
  • 우리는 실제로 파일이 교체할 올바른 shebang으로 시작하는지 확인하고 필요한 만큼 적은 바이트를 읽습니다( zsh다른 쉘은 임의의 바이트 값을 처리할 수 없기 때문에 여기서 필요합니다).
  • 우리는 #!/bin/sh -이것이 스크립트의 올바른 shebang /bin/sh( #!/bin/bash -올바른 shebang이 될 것임 /bin/bash)을 대체품으로 사용합니다. 바라보다왜 "#!/bin/sh -" shebang에 "-"가 있나요?더 알아보기.

파일 덮어쓰기 오류는 종료 상태에 보고되지만 디렉터리 트리 탐색 오류는 보고되지 않으며 파일 읽기 오류는 추가될 수 있지만 보고되지 않습니다.

아무튼 그냥 교체정확히 #!/bin/bash, , bash와 같이 통역사로 사용되는 다른 shebang 대신 . 이를 위해서는 무엇을 해야 할지 결정해야 합니다. 옵션 이지만 이에 상응하는 항목 은 없습니다 .#! /bin/bash#! /bin/bash -Oextglob#! /usr/bin/env bash#! /bin/bash -efu-efush-Oextglobsh

다음과 같이 가장 간단한 경우를 지원하도록 확장할 수 있습니다.

#! /bin/zsh -
LC_ALL=C # work with bytes instead of characters.
zmodload zsh/system || exit

minlength=11 # length of "#!/bin/bash"
maxlength=1024 # arbitrary here.

ret=0
for file in **/*(N.L+$minlength);do
  if
    sysread -s $maxlength buf < $file &&
      [[ $buf =~ $'(^#![\t ]*((/usr)?/bin/env[ \t]+bash|/bin/bash)([ \t]+-([aCefux]*))?[ \t]*)\n' ]]
  then
    shebang=$match[1] newshebang="#!/bin/sh -$match[5]"
    print -r -- ${(r[$#shebang])newshebang} 1<> $file || ret=$?
  fi
done
exit $ret

여기에는 다양한 shebang이 허용되며 지원되는 많은 옵션도 있습니다. 이 옵션은 원본과 동일한 크기로 /bin/sh오른쪽 패딩(매개변수 확장 플래그 사용)되어 새 shebang에서 재현됩니다 .r[length]

답변4

파일은 긴 바이트 시퀀스입니다. 대체하려면 bash기본적으로 sh2바이트를 삭제해야 합니다(UTF-8 또는 이와 유사한 것으로 가정) ba. 파일에는 구멍이 있을 수 없으므로 처음부터 모든 내용을 sh2바이트 앞의 파일에 기록해야 합니다.

이를 위해서는 전체 파일을 다시 작성하거나 최소한 변경된 부분부터 시작해야 합니다.

이를 수행하는 몇 가지 방법이 있습니다.바꾸다예를 들어 형식이 허용하는 경우 전체 파일을 다시 쓰지 않고 무고한 공백이 있는 경우 허용되는 답변을 참조하세요.

관련 정보