14

I have a script that calls a program (specifically, ttf2afm, part of tetex 3.0) that sometimes segfaults and sometimes doesn't. The information I need is always printed out before it segfaults, but I'm having a hard time stopping the pipe redirection from failing and not outputting anything to the pipe when the program fails.

I've tried redirecting through a FIFO, parenthesizing the process with a true at the end, executing from a shell function and encasing in sh -c, but the script never seems to let the process output anything, redirected or otherwise–not even to stderr.

I know it is capable of output, being that it's perfectly capable of giving it from the command-line, but not from a script for some reason.

My question is, is there any way for the script to ignore the fact that the program segfaults and give me the output anyway?

I'm running BASH 4.1.10(2)-release.

amphetamachine
  • 5,517
  • 2
  • 35
  • 43

2 Answers2

13

Programs typically buffer their output for efficiency. That is, they accumulate output in a memory area (called a buffer), and they actually get the output out only when the buffer is full or at certain key points in the program. When the program ends normally, it flushes the output buffer (i.e. prints out any data that's left in it). When it segfaults, the content of the buffer is lost.

You don't observe this effect when running the program directly in a terminal because the behavior is different when the program's output is connected to a terminal (as opposed to a regular file or a pipe). In a terminal, the default behavior is to flush the buffer at the end of each line. Therefore you'll see every complete line that's produced up to the point when the program segfaults.

You can force the program to run in a terminal and collect its output. The simplest way is to run script. There are a number of annoyances that you'll need to work around:

  • script adds a header line to the transcript file, which you'll need to remove afterwards.
  • script doesn't return the status code of the command, so you'll need to save it somewhere if you want to know about the segfault or any other error.
  • script will cause normal output and error out; you'd better save the error output to a separate file.
export FONT="foo"
script -q -c '
    ttf2afm "$FONT.ttf" 2>"$FONT.ttf2afm-err";
    echo $? >"$FONT.ttf2afm-status"
' "$FONT.ttf2afm-typescript"
tail -n +2 <"$FONT.ttf2afm-typescript" >"foo.afm"
rm "$FONT.ttf2afm-typescript"
if [ "$(cat "$FONT.ttf2afm-status")" -ne 0 ]; then
  echo 1>&2 "Warning: ttf2afm failed"
  cat "$FONT.ttf2afm-err"
fi
4

I finally figured it out through a process of trial-and-error. The solution is kind of convoluted:

(trap 'true' ERR; exec ttf2afm "$FONT") |
grep ...

Apparently the exec causes ttf2afm to take over the subshell process with the trapped error, causing it to operate in an environment where it doesn't matter if it segfaults.

Trapping the all-inclusive ERR signal will stop the subshell from dying and sending a signal to the main script–which will terminate immediately if it does–when the program fails.

The only problem is that the kernel itself will output a whole bunch of stack trace garbage directly to the console device once the process segfaults, so there's no way to prevent it from being output [that I know of], but that doesn't matter as it doesn't affect stdout or stderr.

amphetamachine
  • 5,517
  • 2
  • 35
  • 43
  • 3
    I'm glad if this works for you, but I can confidently claim that the reason it works is not because bash is setting the output buffer size to 0. Bash can't influence the buffering used by ttf2afm directly. I wonder how (trap true ERR; exec ttf2afm "$FONT")| … manages to behave differently from ttf2afm "$FONT" | …. – Gilles 'SO- stop being evil' Jul 25 '11 at 11:51