가장 효율적인 방법으로 수백 개의 파일 중 첫 번째 줄을 재귀적으로 변경하고 싶습니다. 내가 하고 싶었던 작업의 예는 #!/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
-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
여기에는 다양한 shebang이 허용되며 지원되는 많은 옵션도 있습니다. 이 옵션은 원본과 동일한 크기로 /bin/sh
오른쪽 패딩(매개변수 확장 플래그 사용)되어 새 shebang에서 재현됩니다 .r[length]
답변4
파일은 긴 바이트 시퀀스입니다. 대체하려면 bash
기본적으로 sh
2바이트를 삭제해야 합니다(UTF-8 또는 이와 유사한 것으로 가정) ba
. 파일에는 구멍이 있을 수 없으므로 처음부터 모든 내용을 sh
2바이트 앞의 파일에 기록해야 합니다.
이를 위해서는 전체 파일을 다시 작성하거나 최소한 변경된 부분부터 시작해야 합니다.
이를 수행하는 몇 가지 방법이 있습니다.바꾸다예를 들어 형식이 허용하는 경우 전체 파일을 다시 쓰지 않고 무고한 공백이 있는 경우 허용되는 답변을 참조하세요.