1

I have a C program that checks an array for the minimum number. There's a script that will run the program and give it test cases; however, I'm struggling to find out what these lines mean:

if ! diff -u <(echo "1 2 5" | $PROGRAM) <(echo 1); then
    echo "test failed on 1 2 5"
    ((FAILURES++))
fi

So I know diff compares files and then the -u will display a timestamp and which files removed what line, and which files added what line, etc. But I can't seem to understand what's going on with the "<" part behind the echo. I know inside the parentheses, (echo "1 2 5" | $PROGRAM), here PROGRAM is the file to execute the C program, my program gets the right output for the minimum number, but it still prints test failed on 1 2 5, and I can't see why because I don't understand the expression in the if statement.

1

3 Answers 3

2

The two <(cmd1) <(cmd2) arguments were new to me, but apparently they gets replaced with paths to named fifos that cmd1 and cmd2 write to. From man bash(1):

Process Substitution

Process substitution is supported on systems that support named
pipes (FIFOs) or the /dev/fd method of naming open files.  takes
the form of <(list) or >(list).  The process list is run with its
input or output connected to a FIFO or some file /dev/fd.  The
name of this file is passed as an argument to the current command
as the result of the expansion.  If the form is used, writing to
the file will provide input for list.  If the <(list) form is
used, the file passed as an argument be read to obtain the output
of list.

This test shows the fifo names:

echo <(date) <(sleep 1; date)
/dev/fd/63 /dev/fd/62

and this prints the result of reading the fifos:

cat <(date) <(sleep 1; date)
Fri Feb 23 14:23:41 PST 2018 
Fri Feb 23 14:23:42 PST 2018
1
  • Some other shells, including ksh93 support the same syntax for process substitution.
    – fpmurphy
    Commented Feb 23, 2018 at 23:34
1

Since nobody has answered the question (“what is happening on this line of code?” – “I’m struggling to find out what this line means”):

The script is testing the C program.  What it’s doing is basically equivalent to

# Run the program with input “1 2 5” and write its output to a file.  Since the program
# is supposed to check the input for the minimum number, we expect it to output “1”.
echo "1 2 5" | $PROGRAM > file1
# Create a second file that contains the known correct output (minimum)
# for this input (i.e., “1”).
echo 1 > file2
# Compare the files.  diff’s standard output and standard error will go to the stdout
# and stderr of the script, which is the terminal unless the user does I/O redirection.
# The `if` will test diff’s exit status.
if diff -u file1 file2
then
        # Exit status 0 means the files are identical;
        # i.e., the program’s output is correct; i.e., the test passes.  Do nothing.
        :
else
        # Exit status non-zero (probably 1) means that the files are different;
        # i.e., the program’s output is wrong; i.e., the test fails.
        echo "test failed on 1 2 5"
        ((FAILURES++))
fi
rm file1 file2

Why was the script written that way?

A really good question.  Perhaps you can ask the author of the script and relay their answer to us.

  1. There’s no need to use files.  And, OK, they aren’t using files; they are, as Andy Dalton and noelbk have explained, using process substitution — which is pipes, and which is not POSIX-compliant.  And, yes, you need to use either a file or a pipe to capture the output from the program.  But you don’t need either a file or a pipe to hold the correct output.  The script can be rewritten

    if [ "$(echo "1 2 5" | $PROGRAM)" != 1 ]
    then
            echo "test failed on 1 2 5"
            ((FAILURES++))
    fi
    

    which uses a command substitution to capture the output from the program, and then just puts the correct output on the if command line.

  2. Why use the -u option of diff?
    • This option is documented as “output NUM (default 3) lines of unified context”.  So this means that, if you have two files that are 100 lines long, and they are identical except for line 42, then diff -u will show you lines 39-45 (three above and three below the one that’s different).  But this is meaningless when one of the inputs is known to be only one line long, and the other is expected to be only one line long.
    • An undocumented feature of the -u option of diff is that it shows the modification times of the inputs.  But, in the given script, the inputs to diff are process substitutions, which are dynamically created pipes.  So the modification times of each are the current time — in other words, on-screen clutter.
  3. You should always quote shell variables (e.g., "$PROGRAM") unless you have a good reason not to.

Why does the script report that your program failed?

It’s impossible to tell without seeing your program, or at least its output.  Is it possible that your program is including a space in its output, or a carriage return (\r) instead of or in addition to a newline?  Or maybe even an extra newline (i.e., a blank line)?  Do this

echo "1 2 5" | your_program | od -cab
(If od reports that it doesn’t recognize all the options, leave out the one it complains about; e.g., -cb or -ab.)  The output should, of course, be a 1 and a newline (\n).

1

The line of code answers the question: Does the program defined by variable $PROGRAM respond to an input of "1 2 5" by returning the value 1. It is a VERY obfuscated way of performing that task. It could have been written as:

if [ $(echo "1 2 5" | $PROGRAM) -ne 1 ] ; then

You combined a second question into this one (BTW, you really should create a separate question page for each question, for the benefit of others who search but don't ask). The reason you are getting a failure indication even though you think it should succeed -- may be (shot in the dark based upon personal experience) because $PROGRAM isn't sending a newline character after the 1.

2
  • -eq and -ne work only with integers (“whole numbers”). Yes, 1 is an integer, but, since you’re testing $PROGRAM, you don’t know what it might output — it might be something other than an integer. In fact, for all we know, $PROGRAM is required to handle non-integer input — so, for an input of 1.17 2.42 5.83, the correct result might be 1.17. But, if $PROGRAM outputs anything other than an integer, your script code will choke. … … … … … … … … … … … … … … … … … … P.S. Your answer looks a lot like mine. Commented Mar 6, 2018 at 7:26
  • @G-Man - $PROGRAM was written by the OP, so if he says it needs to handle float, so be it. As for the PS, I only now am scanning and see that you have a very similar line of code hidden in there. tldr; then and now. My answer is the first sentence of my text, followed by a three line commentary, followed an single paragraph ad obiter. Commented Mar 6, 2018 at 9:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .