0

I'm currently having trouble doing a command in a Makefile. Particular command that I'm trying to translate into bash is:

diff <(head -n 1 file1.out) < file2.out > file.diff

My current code just compares two files without ignoring the first line in the first file.

${OUTPUT_DIR}/%.diff: ${OUTPUT_DIR}/%.out ${EXPECTED_DIR}/%.out
    @ diff $(word 1,$^) $(word 2,$^) > $@; \
    if [ $$? -eq 0 ]; then \
        echo "\t\t\tOK"; \
        echo "------------------------------"; \
    else \
        echo "\t\t\tFailed"; \
        cat $@; \
        echo "------------------------------"; \
    fi

I'm having trouble converting the terminal command to the Makefile (current problem is that it just copies the 'if' and prints it out). Some help would be nice explaining how to convert it as bash and Makefiles are hard for me to parse.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • The `diff <(...) < file2` command lacks a `-` character to tell diff where stdin comes in the comparison. I presume you meant `diff <(...) - < file2`, or perhaps `diff <(...) file2`? – ndim Feb 28 '23 at 15:02
  • Do you want to compare just the first line of `file1` (that is what `head -n1 file1` produces) or the content of `file1` with the first line removed (that is what e.g. `sed 1d` produces)? – ndim Feb 28 '23 at 15:04
  • Littering your Makefile with `@` is doing yourself a disservice. Remove it so you can see what `make` is doing. Then don't put it back; use `make -s` if you genuinely don't care what `make` is doing. – tripleee Feb 28 '23 at 15:09
  • Your command _is_ already Bash. The default shell in `make` is not Bash, though, so you will probably get a syntax error unless you separately override that. See also [Why is testing “$?” to see if a command succeeded or not, an anti-pattern?](https://stackoverflow.com/questions/36313216/why-is-testing-to-see-if-a-command-succeeded-or-not-an-anti-pattern) – tripleee Feb 28 '23 at 15:10

2 Answers2

0

With some guesses as to what you actually want to do, I would write this as follows (assuming I actually want to use the <(...) process substitution):

${OUTPUT_DIR}/%.diff: ${OUTPUT_DIR}/%.out ${EXPECTED_DIR}/%.out
    if diff <(sed 1d $(word 1,$^)) $(word 2,$^) > $@.$$$$.tmp; then \
        printf "\t\t\tOK\n"; \
        mv -f $@.$$$$.tmp $@; \
        echo "------------------------------"; \
    else \
        printf "\t\t\tFailed\n"; \
        cat $@.$$$$.tmp; \
        rm -f $@.$$$$.tmp; \
        echo "------------------------------"; \
        exit 1; \
    fi
  • Check the exit status directly by using if to make it simpler and more robust.
  • Only generate the output %.diff file in the success case so that on the next run, if the %.diff is non-empty again (i.e. the test still fails), the second make run will not succeed.
  • Fix the use of \t in echo by converting that to printf (@tripleee).

Or, taking a few hints from @tripleee's solution while ensuring failed tests alway result in a failed make run:

output/%.diff: output/%.out expected/%.out
    if sed 1d $(word 1,$^) | diff - $(word 2,$^) > $@.$$$$.tmp; then \
        echo "OK   $<"; \
        mv -f $@.$$$$.tmp $@; \
    else \
        echo "FAIL $<"; \
        cat $@.$$$$.tmp; \
        rm -f $@.$$$$.tmp; \
    fi

If you have a large number of test cases, it does make sense to write @ before the if, so that a make run will only list all the working test cases like

OK   output/foo.out
OK   output/bar.out
OK   output/bla.out
OK   output/moo.out
FAIL output/meh.out
1c1
< the line which should be the same
---
> the line which should be the same but is not

A repeat make run will then just print the first failed case:

FAIL output/meh.out
1c1
< the line which should be the same
---
> the line which should be the same but is not
ndim
  • 35,870
  • 12
  • 47
  • 57
  • 1
    Separately, `echo "\t"` simply prints `\t`. You probably want to convert to `printf` if you want to preserve that part. – tripleee Feb 28 '23 at 15:14
0

diff by itself already produces an error and prints the differences if there are any, so your code to separately examine its exit code is superfluous.

Here is a refactoring without any Bashisms, which should work on any POSIX sh that make might run.

${OUTPUT_DIR}/%.diff: ${OUTPUT_DIR}/%.out ${EXPECTED_DIR}/%.out
    head -n 1 $(word 1,$^) \
    | diff - $(word 2,$^) > $@

If you know that make will always run Bash, and your code works in Bash, the code you already have is precisely correct for Make, too.

${OUTPUT_DIR}/%.diff: ${OUTPUT_DIR}/%.out ${EXPECTED_DIR}/%.out
    diff <(head -n 1 $(word 1,$^)) < $(word 2,$^) >$@

though on all the systems I could lay my hands on, this syntax produces diff: missing operand after '/dev/fd/63' (you probably want to remove the < before $(word 2,$^)).

To also get the diff output printed to the console in case of a failure, try this adornment:

${OUTPUT_DIR}/%.diff: ${OUTPUT_DIR}/%.out ${EXPECTED_DIR}/%.out
    ! head -n 1 $(word 1,$^) \
    | diff - $(word 2,$^) \
    | tee $@ | grep ^

The ! ... grep ^ is to capture the failure exit code from diff.

head -n 1 obviously only prints the first line from the file; if you really meant to print all lines except the first, try tail -n +2 instead.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • `cat`ing the generated diff in case of a non-empty diff can be useful to see the problem immediately. – ndim Feb 28 '23 at 15:17
  • @ndim Good point; added a `tee` solution – tripleee Feb 28 '23 at 15:20
  • I still see two problems here: `head -n1 file1` does not "ignore the first line" (which I presume OP actually wants instead of `head -n1`), and that the subsequent `make` runs after the first `make` run will exit successfully, regardless of whether the `diff` in the first `make` run succeeded or failed. – ndim Feb 28 '23 at 15:32
  • True - the OP's question is inherently unclear. Try `tail +2 file1` or switch to `sed 1q` like in @ndim's answer. I added a `grep` to fix the exit code from `tee` - thanks for noticing that, too. – tripleee Feb 28 '23 at 15:34
  • Do not forget the `-n` in `tail -n +2 file1`, and do use `sed 1d file1` to skip the first line. `sed 1q file1` just prints up to the first line just like `head -n1 file1` does. – ndim Feb 28 '23 at 15:38
  • The `-n` is important for portability, but both Linux and MacOS `tail` seem to cope with `tail +2`. I added a final paragraph about that. – tripleee Feb 28 '23 at 15:41