
가능한 가장 효율적인 방법으로 수백 개의 파일 중 첫 번째 줄을 재귀적으로 변경하고 싶습니다. 내가 하고 싶은 일의 예는 로 변경하는 것이므로 #!/bin/bash
다음 #!/bin/sh
명령을 생각해 냈습니다.
find ./* -type f -exec sed -i '1s/^#!\/bin\/bash/#!\/bin\/sh/' {} \;
하지만 내가 이해하기로는 이런 방식으로 sed가 전체 파일을 읽고 원본을 교체해야 한다는 것입니다. 이를 수행하는 더 효율적인 방법이 있습니까?
답변1
예, sed -i
파일 전체를 읽고 다시 씁니다. 줄 길이가 변경되므로 다른 모든 줄의 위치도 이동해야 합니다.
...하지만 이 경우에는 줄 길이를 실제로 변경할 필요가 없습니다. 대신 해시뱅 줄을 #!/bin/sh␣␣
두 개의 후행 공백으로 바꿀 수 있습니다. OS는 해시뱅 라인을 구문 분석할 때 해당 항목을 제거합니다. (또는 두 개의 개행 문자 또는 개행 + 해시 기호를 사용하십시오. 둘 다 쉘이 결국 무시할 추가 행을 생성합니다.)
우리가 해야 할 일은 파일을 자르지 않고 처음부터 쓰기 위해 파일을 여는 것뿐입니다. 일반적인 리디렉션 >
은 >>
그렇게 할 수 없지만 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을 담을 만큼 크지 않은 파일을 조사할 필요가 없습니다(우리는
.
와 동등한 것으로 사용-type f
하므로 이미 파일에서 inode 정보를 검색하고 있으므로 거기서 크기를 확인하는 것이 좋습니다 ). zsh
우리는 실제로 파일이 교체할 올바른 shebang으로 시작하는지 확인하고 필요한만큼 적은 바이트를 읽습니다(여기서는 다른 쉘이 임의의 바이트 값을 처리할 수 없기 때문에 그래야 합니다 ).- 우리는 스크립트
#!/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
-efu
sh
-Oextglob
sh
다음과 같은 가장 쉬운 사례를 지원하도록 확장할 수 있습니다.
#! /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
여기서는 원본과 동일한 크기로 /bin/sh
오른쪽 패딩(매개변수 확장 플래그 포함 ) 된 새 shebang에서 재현되는 다양한 지원 옵션을 사용하여 다양한 shebang을 허용합니다 .r[length]
답변4
파일은 하나의 긴 연속 바이트 범위입니다. bash
를 로 바꾸려면 sh
기본적으로 를 구성하는 2바이트(UTF-8 또는 이와 유사한 것으로 가정)를 제거해야 합니다 ba
. 파일에는 구멍이 있을 수 없으므로 에서 시작하는 모든 내용은 sh
파일에 2바이트 먼저 기록되어야 합니다.
이를 위해서는 전체 파일을 다시 작성하거나 최소한 변경된 부분부터 시작해야 합니다.
다음과 같은 방법이 있습니다.바꾸다예를 들어 전체 파일을 다시 쓸 필요 없이 형식이 허용하는 경우 무고한 공백이 있는 파일의 바이트 수는 허용되는 답변을 참조하세요.