나는 종종 다음과 같은 일을 하고 싶습니다:
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