파일을 자체적으로 전송하기 위한 스폰지에 대한 표준 대안이 있습니까?

파일을 자체적으로 전송하기 위한 스폰지에 대한 표준 대안이 있습니까?

나는 종종 다음과 같은 일을 하고 싶습니다:

 cat file | command > file

(이것은 분명히 작동하지 않습니다). 내가 본 유일한 해결책은 sponge다음과 같습니다.

 cat file | command | sponge file

불행하게도 sponge나는 그것을 사용할 수 없습니다(또한 그것을 설치하거나 다른 패키지를 설치할 수도 없습니다).

매번 여러 명령(임시 파일로 파이프, 원본 파일로 다시 파이프, 임시 파일 삭제)으로 분할하지 않고도 이 작업을 수행할 수 있는 보다 표준적이고 빠른 방법이 있습니까? tee예를 들어, 이것을 시도했는데 작동하는 것 같지만 일관성 있고 안전한 솔루션입니까?

답변1

쉘 함수 대체 sponge:

mysponge () (
    append=false

    while getopts 'a' opt; do
        case $opt in
            a) append=true ;;
            *) echo error; exit 1
        esac
    done
    shift "$(( OPTIND - 1 ))"

    outfile=$1

    tmpfile=$(mktemp "$(dirname "$outfile")/tmp-sponge.XXXXXXXX") &&
    cat >"$tmpfile" &&
    if "$append"; then
        cat "$tmpfile" >>"$outfile"
    else
        if [ -f "$outfile" ]; then
            chmod --reference="$outfile" "$tmpfile"
        fi
        if [ -f "$outfile" ]; then
            mv "$tmpfile" "$outfile"
        elif [ -n "$outfile" ] && [ ! -e "$outfile" ]; then
            cat "$tmpfile" >"$outfile"
        else
            cat "$tmpfile"
        fi
    fi &&
    rm -f "$tmpfile"
)

mysponge쉘 함수는 표준 입력에서 사용 가능한 모든 데이터를 임시 파일로 전달합니다.

모든 데이터가 임시 파일로 리디렉션되면 수집된 데이터가 함수 매개변수에 지정된 파일에 복사됩니다. 데이터가 그렇지 않은 경우추가의파일에(즉, -a사용되지 않음), 주어진 출력 파일 이름이 기존 일반 파일을 참조하는 경우 해당 파일이 존재하지 않으면 이 작업이 수행됩니다 mv(파일이 기존 일반 파일인 경우 먼저 GNU 모드를 사용하여 파일을 복사하려고 시도합니다) 임시 파일로 전송됩니다 chmod.) 출력이 일반 파일(이름이 지정된 파이프, stdout 등)이 아닌 경우 데이터는 cat.

명령줄에 파일이 제공되지 않으면 수집된 데이터가 표준 출력으로 전송됩니다.

마지막으로 임시 파일이 삭제됩니다.

함수의 각 단계는 이전 단계의 성공적인 완료에 따라 달라집니다. 명령이 실패하면(중요한 데이터가 포함될 수 있음) 임시 파일을 삭제하려는 시도가 이루어지지 않습니다.

지정된 파일이 없으면 사용자의 기본 권한 등을 사용하여 생성되고 표준 입력에서 도착하는 데이터가 여기에 기록됩니다.

mktemp유틸리티는 표준은 아니지만 일반적으로 사용할 수 있습니다.

위 함수는 다음에 설명된 동작을 모방합니다.수동sponge데비안 의 패키지 moreutils.


대신 사용하는 것은 실행 가능한 옵션 tee이 아닙니다 . sponge당신은 그것을 시도해 보았고 그것이 당신에게 효과가 있는 것 같다고 말했습니다. 작동할 수도 있고 작동하지 않을 수도 있습니다. 이는 파이프라인에서 명령을 시작하는 타이밍(동일한 시간에 시작됨)과 입력 데이터 파일의 크기에 따라 달라집니다.

tee다음은 사용법이 작동하지 않는 경우를 보여주는 예입니다 .

원본 파일은 200000바이트이지만 파이프된 후에는 32KiB로 잘립니다(아마도 내 시스템의 일부 버퍼 크기에 해당함).

$ yes | head -n 100000 >hello
$ ls -l hello
-rw-r--r--  1 kk  wheel  200000 Jan 10 09:45 hello
$ cat hello | tee hello >/dev/null
$ ls -l hello
-rw-r--r--  1 kk  wheel  32768 Jan 10 09:46 hello

답변2

Perl이 필요한 짧은 bash 스크립트가 있습니다.
https://github.com/ildar-shaimordanov/perl-utils#sponge

두 번째 스크립트는 moreutils의 버전을 즉시 대체해야 합니다.

독립형 Perl 스크립트 버전도 있습니다.

답변3

function wf() {
    #create a temporary file
    local tmpf="${1}_$(< /dev/urandom tr -dc A-Za-z0-9 | head -c16)"
    #redirect the result
    cat > $tmpf
    #replace the original file
    mv -f $tmpf "${1}"
}

다음으로 이 기능을 사용합니다.

grep "error" messages.log | wf messages.log

답변4

파리를 쫓아내기 위해 대포를 사용하는 이유는 무엇입니까? 가능한 해결책은 다음과 같습니다.

stdin-to-file () {
local function_name="${FUNCNAME[0]}"
local tmp_file
local append=false
local exit_code=0
for (( i=1; i<=$#; i++ )); do
    if [[ ${!i} = -- ]]; then
        set -- "${@:1:i-1}" "${@:i+1}"
        break
    fi
    if [[ ${!i} = -a || ( --append = ${!i}* && $(expr length "${!i}") -ge 3 ) ]]; then
        append=true
        set -- "${@:1:i-1}" "${@:i+1}"
        ((i--))
        continue
    fi
done
if [[ $# -ne 1 || -t 0 ]]; then
    echo "$function_name: Wrong number of arguments or missing stdin." >&2
    return 1
fi
tmp_file="$(mktemp "/tmp/$(basename -- "$1")-XXXXXXXXXXXX")" &&
cat > "$tmp_file" &&
if $append; then
    cat "$tmp_file" >> "$1"
else
    cat "$tmp_file" > "$1"
fi ||
exit_code=$?
rm -f -- "$tmp_file"
if [[ $exit_code != 0 ]]; then
    echo "$function_name: An error has occurred." >&2
fi
return $exit_code
}

그 다음에:

cat file | command | stdin-to-file file

추가 사항:

cat file | command | stdin-to-file -a file

또는:

cat file | command | stdin-to-file --append file

관련 정보