파이프 전체에서 단일 파일을 입력 및 출력으로 처리

파이프 전체에서 단일 파일을 입력 및 출력으로 처리

좋은 저녁이에요,

일부 파이프된 명령을 사용하여 파일 내용을 필터링한 다음 결과를 동일한 파일에 다시 쓰고 싶습니다. 알아요, 제가 쓴 대로 그렇게 할 수는 없어요. 기다리다 …

이것은 내가 가지고 있는 bash 스크립트의 일부입니다.

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

그래서 대신 프로세스 대체를 사용하면 성공할 수 있다고 생각했습니다. 나는 다음과 같이 썼다:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

이것도 아무것도 해결되지 않았습니다. 나는 임시 파일과 같은 어딘가에 내 입력 파일 콘텐츠를 « 저장 »하는 프로세스 대체를 기대했습니다. 이음새도 프로세스 대체를 이해하지 못했습니다.

"inplace" 에디션에 대한 스레드를 읽었지만 이 기사에서는 sed -i또는 같은 일부 바이너리의 특수 옵션을 강조했지만 sort -o일반적인 솔루션이 필요합니다(파이프된 명령에 적합해야 함을 의미합니다).

먼저 '파이프 표준 방식'이 이를 수행할 수 없는 이유는 무엇입니까? 아래에서는 무슨 일이 일어나고 있습니까? :/문제를 어떻게 해결해야 합니까? 누군가 제발 부탁드려도 될까요?설명하다나 이게 다 뭐야?

감사합니다.

답변1

앞서 언급했듯이 스펀지는더 많은 유틸리티중대하다. moreutils 종속성을 피하기 위해 이 스크립트를 사용하여 에뮬레이트합니다.

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

다음과 같이 사용할 수 있습니다.

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

명령이 시작되기 전에 리디렉션이 발생하고 출력 리디렉션이 출력 파일을 자르기 때문에 단순 출력 리디렉션으로는 이 작업을 수행할 수 없습니다.

즉, grep(파이프라인의 첫 번째 단순 명령)이 시작될 때 마지막 리디렉션에서 이미 입력/출력 파일이 잘렸습니다.

내가 아는 한 실제로 내부 편집을 수행하는 표준 UNIX 유틸리티는 없습니다. sed -i임시 파일로만 에뮬레이션합니다. 그 이유는 파이프라인 단계가 실패할 경우 실제 내부 필터링이 파일을 쉽게 손상시킬 수 있기 때문인 것 같습니다.

아래에서 무슨 일이 일어나고 있는지에 관해서는 둘 다 |<()번에 IO를 버퍼로 전달하는 시스템 파이프를 사용합니다. 메커니즘은 임시 파일(어차피 실제(파일 시스템) 파일이 아님)을 생성하지 않으며 한 번에 전체 입력을 메모리에 보관하는 것을 방지하려고 합니다.

답변2

동일한 파일에서 입력하고 출력하려면 시도해 볼 수 있습니다.스펀지. 설명에 따르면 다음과 같습니다.

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

sed '...' file | grep '...' | sponge [-a] file그래서 당신은 입력을받는 것과 같은 것을 가질 수 있습니다파일그리고 동일하게 출력파일.


반면, 임시 파일을 사용하는 것도 동일한 파일을 입력 및 출력으로 사용하는 좋은 방법입니다. 다음과 같이 임시 파일을 초기화할 수 있습니다.

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

그러면 이 스크립트가 실행되는 디렉터리에 "tempFile"이라는 임시 파일이 생성됩니다. 확장자는 "XXXX"입니다. 여기서 x는 현재 프로세스 번호와 임의 문자의 조합(예: tempFile.AVm7)으로 대체됩니다.

이제 다음과 같이 파이프(또는 파이프된 명령)를 수정할 수 있습니다.

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

필터 후 다음과 같이 임시 파일을 원본 파일로 이동할 수 있습니다.

mv "$tempfile" "$filepath"

이렇게 하면 임시 파일이 제거되고 필터링된 원본 파일은 그대로 유지됩니다. 그러나 때로는 필요하지 않고 파기되지도 않은 임시 파일을 많이 생성하게 될 수 있으므로 더 이상 필요하지 않은 경우 스크립트가 끝난 후 모든 임시 파일을 삭제하여 디렉터리를 정리하는 것이 좋습니다. . 이에 대한 루틴을 다음과 같이 작성할 수 있습니다.

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

remove_temp_files그런 다음 스크립트 끝에서 루틴을 호출하여 위에 설명된 형식으로 생성된 모든 임시 파일을 제거할 수 있습니다.

답변3

사용여기 문서그리고명령 대체이 경우 표준 방법은 다음과 같습니다.

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

다른 질문에 대해서는 이전에 많은 질문에서 설명했습니다.

관련 정보