问题
Is there a simple way to "rewind" /dev/stdin
inside my bash script which already read all or some portion from the input pipe?
Application: I wrote a simple MDA that in part 1, reads a single email from fetchmail line by line, like so:
while read -a linA; do
echo -e "$[++linenum]:\t${#linA[@]},${linA[*]}" > /dev/null # verbose
[ "${linA[0]}" = "Date:" ] && unset linA[0] && mailDate="${linA[*]}"
[ "${linA[0]}" = "Subject:" ] && unset linA[0] && mailSubject="${linA[*]}"
[ "$mailSubject" = "Courtesy Fill Notification" ] || break # if wrong subject then thank you, we're done with this mail
done
and at the end of processing, I wish to save the entire message into a file, both for debugging, and so that the writer-side of the pipe sees that its entire output had been read, and not return failure (therefore keeping the message as unread in the mailbox).
回答1:
Reading a pipe is destructive; there is no way to seek on a pipe:
ESPIPE (29): Illegal seek
The error number is from MacOS X, but the name is traditional (and POSIX-mandated) and gives an indication of where the restriction comes from.
So, if you want to reprocess input in a shell script, you will need to stash the standard input away in a file so you can reprocess it:
tmp=${TMPDIR:-/tmp}/xx.$$ # Consider mktemp or something
trap "rm -f $tmp.1; exit 1" 0 1 2 3 13 15 # Exit, HUP, INT, QUIT, PIPE, TERM
tee $tmp.1 |
while read -a linA
do
...
done
...reprocess $tmp.1 here...
rm -f $tmp.1
trap 0
exit $exit_status
The only trap to watch is that because of the pipeline, variables set in the while
loop are not accessible in the parent shell. If that's a problem, you can use:
tee $tmp.1 |
{
while read -a linA
do
...
done
...reprocess $tmp.1 here...
}
The braces group the statements for I/O redirection purposes. The tee
process will have completed because of EOF so the file will be complete when the while read
detects EOF, so it is safe to access $tmp.1
after the loop.
The one thing to watch is that tee
will make this unresponsive to terminal input. Files won't be a problem; piped input is unlikely to be a problem. However, tee
will probably fully buffer its output, rather than line buffering its output, so the read loop may not see anything until you've typed many lines of input.
回答2:
Not really, no.
You'll just have to append each line into a variable as you read it and clear the variable as needed.
回答3:
how about:
tmpfile=$(mktemp)
while read -a linA; do
echo -e "$[++linenum]:\t${#linA[@]},${linA[*]}" > /dev/null # verbose
[ "${linA[0]}" = "Date:" ] && unset linA[0] && mailDate="${linA[*]}"
echo "${linA}">>$tmpfile
done
mv $tmpfile fulltext.txt
I think it's better way because you read message just once
回答4:
Try exec < /dev/stdin
which might work under Linux.
来源:https://stackoverflow.com/questions/9312230/rewinding-stdin-in-a-bash-script