How to check return value from the shell directive

2019-02-07 22:58发布

问题:

In my Makefile, I need to test if the current directory is an SVN repo or not and if it is not I want to indicate an error using the $(error) directive in Makefile.

So I plan to use the return value of $(shell svn info .) but I'm not sure how to get this value from within the Makefile.

Note: I'm not trying to get the return value in a recipe, but rather in the middle of the Makefile.

Right now I'm doing something like this, which works just because stdout is blank when it is an error:

SVN_INFO := $(shell svn info . 2> /dev/null)
ifeq ($(SVN_INFO),)
    $(error "Not an SVN repo...")
endif

I'd still like to find out if it is possible to get the return value instead within the Makefile.

回答1:

How about using $? to echo the exit status of the last command?

SVN_INFO := $(shell svn info . 2> /dev/null; echo $$?)
ifeq ($(SVN_INFO),1)
    $(error "Not an SVN repo...")
endif


回答2:

This worked fine for me - based on @eriktous' answer with a minor modification of redirecting stdout as well to skip the output from svn info on a valid svn repo.

SVN_INFO := $(shell svn info . 1>&2 2> /dev/null; echo $$?)
ifneq ($(SVN_INFO),0)
    $(error "Not an SVN repo...")
endif


回答3:

If you want to preserve the original output then you need to do some tricks. If you are lucky enough to have GNU Make 4.2 (released on 2016-05-22) or later at your disposal you can use the .SHELLSTATUS variable as follows.

var := $(shell echo "blabla" ; false)

ifneq ($(.SHELLSTATUS),0)
  $(error shell command failed! output was $(var))
endif

all:
    @echo Never reached but output would have been $(var)

Alternatively you could use a temporary file or play with Make's eval to store the string and/or the exit code into a Make variable. The example below gets this done but I would certainly like to see a better implementation than this embarrassingly complicated version.

ret := $(shell echo "blabla"; false; echo " $$?")
rc := $(lastword $(ret))
# Remove the last word by calculating <word count - 1> and
# using it as the second parameter of wordlist.
string:=$(wordlist 1,$(shell echo $$(($(words $(ret))-1))),$(ret))

ifneq ($(rc),0)
  $(error shell command failed with $(rc)! output was "$(string)")
endif

all:
    @echo Never reached but output would have been \"$(string)\"


回答4:

Maybe something like this?

IS_SVN_CHECKED_OUT := $(shell svn info . 1>/dev/null 2>&1 && echo "yes" || echo "no")
ifne ($(IS_SVN_CHECKED_OUT),yes)
    $(error "The current directory must be checked out from SVN.")
endif


回答5:

I use a couple make functions:

# This function works almost exactly like the builtin shell command, except it
# stops everything with an error if the shell command given as its argument
# returns non-zero when executed.  The other difference is that the output
# is passed through the strip make function (the shell function strips only
# the last trailing newline).  In practice this doesn't matter much since
# the output is usually collapsed by the surroundeing make context to the
# same result produced by strip.  WARNING: don't try to nest calls to this
# function, take a look at OSHELL_CHECKED instead.
SHELL_CHECKED =                                                      \
  $(strip                                                            \
    $(if $(shell (($1) 1>/tmp/SC_so) || echo nonempty),              \
      $(error shell command '$1' failed.  Its stderr should be above \
              somewhere.  Its stdout is in '/tmp/SC_so'),            \
      $(shell cat /tmp/SC_so)))

# "Other" SHELL_CHECKED.  Like SHELL_CHECKED, but uses different file names
# and so may be used with arguments that themselves use SHELL_CHECKED
# or vice versa.  In other words, a hack to allow two-level nesting of
# checked shell calls by hardwiring the call points involved to not both
# use SHELL_CHECKED or OSHELL_CHECKED.
OSHELL_CHECKED =                                                     \
  $(strip                                                            \
    $(if $(shell (($1) 1>/tmp/OSC_so) || echo nonempty),             \
      $(error shell command '$1' failed.  Its stderr should be above \
              somewhere.  Its stdout is in '/tmp/OSC_so'),           \
      $(shell cat /tmp/OSC_so)))

These can then be called like so:

$(call SHELL_CHECKED,some_command $(call OSHELL_CHECKED,other_command some_arg))