rewinding stdin in a bash script

这一生的挚爱 提交于 2019-12-17 20:29:51

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!