명령 출력의 바이트 수와 sha1sum을 얻고 싶습니다.
원칙적으로는 항상 다음과 같이 할 수 있습니다.
BYTES="$( somecommand | wc -c )"
DIGEST="$( somecommand | sha1sum | sed 's/ .*//' )"
...그러나 내가 관심 있는 사용 사례의 경우 somecommand
시간이 많이 걸리고 많은 출력을 생성하므로 한 번만 호출하는 것이 좋습니다.
제가 생각한 방법 중 하나는 바로 이것입니다
evil() {
{
somecommand | \
tee >( wc -c | sed 's/^/BYTES=/' ) | \
sha1sum | \
sed 's/ .*//; s/^/DIGEST=/'
} 2>&1
}
eval "$( evil )"
...효과가 있는 것 같았지만 속으로는 죽을 것 같았습니다.
파이프라인의 여러 세그먼트의 출력을 별도의 변수로 캡처하는 더 나은(더 강력하고 더 일반적인) 방법이 있는지 궁금합니다.
편집: 현재 해결 중인 문제는 bash
이 셸 솔루션에 가장 관심이 있기 때문에 프로그래밍도 많이 수행하므로 zsh
해당 솔루션에도 관심이 있습니다.
EDIT2: Stéphane Chazelas의 솔루션을 로 포팅하려고 시도했지만 bash
제대로 작동하지 않았습니다.
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
cmd | tee >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
위 스크립트를 실행하면 내가 얻는 결과는 다음과 같습니다.
bytes=
checksum=96d89030c1473585f16ec7a52050b410e44dd332
값이 checksum
정확합니다. 왜 bytes
값이 설정되어 있지 않은지 이해가 되지 않습니다 .
EDIT3: 좋습니다. @muru의 팁 덕분에 문제를 해결했습니다.
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
read bytes < <( cmd | tee >(sha1sum > $1) | wc -c ) || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
지금:
bytes=1000
checksum=96d89030c1473585f16ec7a52050b410e44dd332
안타깝게도...
bytes_and_checksum
... 위 예제의 경우보다 훨씬 더 많은 출력을 생성하는 경우 내 함수가 중단됩니다(교착 상태?).cmd
다시 드로잉 보드로 돌아가서...
답변1
임시 파일을 사용하는 것이 더 쉽습니다. 존재하다 zsh
:
(){set -o localoptions -o pipefail; local IFS
{cmd} > >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
} =()
많은 wc
구현에는 출력하는 숫자 주위에 공백이 포함되어 있습니다. read
기본값은 $IFS
제거하는 것입니다.
의 종료 상태가 sha1sum
손실됩니다.
임시 파일이 생성될 =()
때 내용이 전혀 출력되지 않습니다 . 임시 파일은 제공된 명령(여기서는 익명 함수)이 반환되면 자동으로 삭제됩니다.
의 출력 은 두 번 리디렉션되므로 d에 의해 내부적으로 처리되므로 cmd > file | other-cmd
여기 에는 to 및 to 가 있습니다 . zsh가 프로세스 리디렉션이 완료될 때까지 기다리도록 래핑 합니다 .cmd
tee
zsh
sha1sum
wc
cmd
{...}
여기서 두 출력은 몇 바이트 이하로 보장 sha1sum
되며 wc
파이프로 전송될 수도 있으며 동시에 해당 파이프에서 읽을 필요가 없습니다(zsh는 다음을 select()
위한 인터페이스를 가지고 있으므로 이를 수행할 수 있습니다). / poll()
, 그러나 bash는 아님). 이는 교착 상태를 일으키지 않고 순차적으로 수행될 수 있으므로 여기에 간단한 버전이 있습니다.다양한 변수로.
Linux 기반 시스템( /dev/fd/x
파이프 x
의 fd가 명명된 파이프처럼 동작하는 경우):
{
IFS=$' \t' read bytes < <(cmd 3<&- | tee >(sha1sum > /dev/fd/3) | wc -c)
IFS=$' \t' read sum rest <&3
} 3< <(:)
(Bash에서도 작동합니다).
더 큰 출력에서 발생하는 교착 상태에 대한 자세한 내용은 다음을 참조하세요.tee + cat: 출력을 여러 번 사용한 다음 결과를 연결합니다..
답변2
백업을 사용하고 있습니다세게 때리다"가정된 파일 이름"을 인수로 사용하는 다음 도우미 "중간" 함수가 있는 스크립트(아래 tar.gz 예제 참조):
function pipesum
{
tee >(sha1sum | awk --assign F="${1##*/}" '$2=F' > "${1?}.sha1")
}
function pipelen
{
tee >(wc -c > "${1?}.len")
}
function pipesumlen
{
tee >(sha1sum | awk --assign F="${1##*/}" '$2=F' > "${1?}.sha1") >(wc -c > "${1?}.len")
}
function pipechecksum
{
tee >(sha1sum --quiet -c <(awk '$2="-"' "${1?}") >&2)
}
예:
$ echo 123 | pipesumlen filename
123
$ ls filename*
filename.len filename.sha1
$ cat filename*
4
a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0 filename
$ echo 123 | pipechecksum filename.sha1
123
$ echo 1234 | pipechecksum filename.sha1
1234
-: FAILED
sha1sum: WARNING: 1 computed checksum did NOT match
나는 이것을 다음과 같이 시간이 많이 걸리고 CPU 및 IO를 많이 소비하는 스크립트에서 사용하고 있습니다.
tar |
pipesumlen mybackup.tar |
gzip > mybackup.tar.gz
<mybackup.tar.gz gunzip |
pipechecksum mybackup.tar.sha1 |
xz > mybackup.tar.xz
그래서 무작위 메모리/디스크 비트 플립을 기반으로 백업을 확인했습니다. "mybackup.tar"가 실제로 생성되고 체크섬되는 것처럼 "mybackup.tar.sha1" 파일이 생성됩니다. 실제로 이 예에서는 압축되지 않은 데이터가 디스크에 기록되지 않습니다.
경고: pipechecksum
오류가 발생하더라도 스크립트는 종료되지 않습니다 set -euo pipefail
. pipechecksum
체크섬이 일치하지 않을 때 0이 아닌 값을 반환하는 대안:
function pipechecksum
{
{ tee /dev/fd/$N | sha1sum --quiet -c <(awk '$2="-"' "${1?}") >&2; } {N}>&1
}
좋아 보이는데, 오늘 막 가져왔는데 아직 확정된 것으로 볼 수는 없습니다.