Unable to capture command exit code in makefile - bash

I'm trying to setup my first makefile and am hitting a block at step 1. In my shell script, I did this:
which brew | grep 'brew not found' >/dev/null 2>&1
if [ $? == 0 ]; then
xcode-select --install
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi
This worked just fine as a bash script. After some googling, for a Makefile, I've so far come up with this one command:
BREW_INSTALLED = $(shell which brew | grep 'brew not found' >/dev/null 2>&1; echo $$?)
However, running it gets me
make: BREW_INSTALLED: No such file or directory
I'm equally unsure when I should be adding # to a command (seems like anything I don't want to output?).
I'm currently on GNU Make 3.81.

There are several odds in this line:
BREW_INSTALLED = $(shell which brew | grep 'brew not found' >/dev/null 2>&1; echo $$?)
In case of success, which writes its output to stdout, in case of failure to stderr. You are trying to capture the error message on stdout.
To feed the stderr of which to grep, you would need to write
which brew 2>&1 >/dev/null | grep 'brew not found'
(The order of 2>&1 and > also matters).
But you should not rely on the specific error message of which.
But you already get the return code you want from which, so you don't need grep at all.
Which returns the number of failed arguments, or -1 when no `programname' was given.
https://linux.die.net/man/1/which
Consider using grep -q 'expression' to supress output instead of redirecting stdout and stderr.
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected.
https://linux.die.net/man/1/grep
And the error message you get has nothing to do with what I'm writing above. This means the shell is trying to run BREW_INSTALLED as command, which probably means that make puts it at the beginning of a new shell.
Maybe you wrote it after a tabspace? see https://www.gnu.org/software/make/manual/html_node/Recipe-Syntax.html
To capture the return code (as string!):
BREW_INSTALLED := $(shell which brew >/dev/null 2>&1; echo $$?)

A typical makefile would check the presence of needed tools like this:
BREW := $(shell which brew)
# Check if variable brew is empty
ifeq ($(BREW),)
$(error brew not found)
else
$(info brew found: $(BREW))
endif
all:
#echo "Do something with brew"
$(BREW) --version
Note: There must be no tabspaces in the first two indented lines.
The two Recipe lines if the all Rule have to be indented with tabs.
The # at the beginning of a recipe supresses echoing: https://www.gnu.org/software/make/manual/html_node/Echoing.html

Related

Bash. Parse error output without showing error

I want to get and parse the python (python2) version. This way (which works):
python2 -V 2>&1 | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/'
For some reason, python2 is showing the version using the -V argument on its error output. Because this is doing nothing:
python2 -V | sed 's/.* \([0-9]\).\([0-9]\).*/\1\2/'
So it needs to be redirected 2>&1 to get parsed (stderr to stdout). Ok, but I'd like to avoid the error shown if a user launching this command has no python2 installed. The desired output on screen for a user who not have python2 installed is nothing. How can I do that? because I need the error output shown to parse the version.
I already did a solution doing before a conditional if statement using the hash command to know if the python2 command is present or not... so I have a working workaround which avoids the possibility of launching the python2 command if it is not present... but just curiosity. Forget about python2. Let's suppose is any other command which is redirecting stderr to stdout. Is there a possibility (bash trick) to parse its output without showing it if there is an error?
Any idea?
Print output only if the line starts with Python 2:
python2 -V 2>&1 | sed -n 's/^Python 2\.\([0-9]*\).*/2\1/p'
or,
command -v python2 >/dev/null && python2 -V 2>&1 | sed ...
Include the next line in your script
command python2 >/dev/null 2>&1 || {echo "python2 not installed or in PATH"; exit 1; }
EDITED: Changed which into command

Is there a way for me to get specific words from an output of a command in bash?

For example, when running the dpkg command,
dpkg -s autofs
I would get an output like
dpkg-query: package 'autofs' is not installed and no information is available
But I just want to get the
not installed
part so that I can further use it for my script. Is there a command that can help me with it?
First of all, the words not installed are locale specific, so it will fail with anything but English locales. It is also non-predictable because it is not a published API Application Programming Interface.
So, even with some precautions, do not use this:
LC_MESSAGES=C dpkg-query --status autofs 2>&1 | grep -o 'not installed'
Check the return status of the dpkg-query command instead:
#!/usr/bin/env sh
package='autofs'
if dpkg-query --status "$package" >/dev/null 2>&1; then
printf 'Package %s is installed!\n' "$package"
else
printf 'Package %s is not installed!\n' "$package"
fi

Run Shell scripts without having messages [duplicate]

I want to make my Bash scripts more elegant for the end user. How do I hide the output when Bash is executing commands?
For example, when Bash executes
yum install nano
The following will show up to the user who executed the Bash:
Loaded plugins: fastestmirror
base | 3.7 kB 00:00
base/primary_db | 4.4 MB 00:03
extras | 3.4 kB 00:00
extras/primary_db | 18 kB 00:00
updates | 3.4 kB 00:00
updates/primary_db | 3.8 MB 00:02
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package nano.x86_64 0:2.0.9-7.el6 will be installed
--> Finished Dependency Resolution
Dependencies Resolved
================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
nano x86_64 2.0.9-7.el6 base 436 k
Transaction Summary
================================================================================
Install 1 Package(s)
Total download size: 436 k
Installed size: 1.5 M
Downloading Packages:
nano-2.0.9-7.el6.x86_64.rpm | 436 kB 00:00
warning: rpmts_HdrFromFdno: Header V3 RSA/SHA256 Signature, key ID c105b9de: NOKEY
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
Importing GPG key 0xC105B9DE:
Userid : CentOS-6 Key (CentOS 6 Official Signing Key) <centos-6-key#centos.org>
Package: centos-release-6-4.el6.centos.10.x86_64 (#anaconda-CentOS-201303020151.x86_64/6.4)
From : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Installing : nano-2.0.9-7.el6.x86_64 1/1
Verifying : nano-2.0.9-7.el6.x86_64 1/1
Installed:
nano.x86_64 0:2.0.9-7.el6
Complete!
Now I want to hide this from the user and instead show:
Installing nano ......
How can I accomplish this task? I will definitely help to make the script more user friendly. In case an error occurs then it should be shown to the user.
I would like to know how to show same message while a set of commands are being executed.
Use this.
{
/your/first/command
/your/second/command
} &> /dev/null
Explanation
To eliminate output from commands, you have two options:
Close the output descriptor file, which keeps it from accepting any more input. That looks like this:
your_command "Is anybody listening?" >&-
Usually, output goes either to file descriptor 1 (stdout) or 2 (stderr). If you close a file descriptor, you'll have to do so for every numbered descriptor, as &> (below) is a special BASH syntax incompatible with >&-:
/your/first/command >&- 2>&-
Be careful to note the order: >&- closes stdout, which is what you want to do; &>- redirects stdout and stderr to a file named - (hyphen), which is not what what you want to do. It'll look the same at first, but the latter creates a stray file in your working directory. It's easy to remember: >&2 redirects stdout to descriptor 2 (stderr), >&3 redirects stdout to descriptor 3, and >&- redirects stdout to a dead end (i.e. it closes stdout).
Also beware that some commands may not handle a closed file descriptor particularly well ("write error: Bad file descriptor"), which is why the better solution may be to...
Redirect output to /dev/null, which accepts all output and does nothing with it. It looks like this:
your_command "Hello?" > /dev/null
For output redirection to a file, you can direct both stdout and stderr to the same place very concisely, but only in bash:
/your/first/command &> /dev/null
Finally, to do the same for a number of commands at once, surround the whole thing in curly braces. Bash treats this as a group of commands, aggregating the output file descriptors so you can redirect all at once. If you're familiar instead with subshells using ( command1; command2; ) syntax, you'll find the braces behave almost exactly the same way, except that unless you involve them in a pipe the braces will not create a subshell and thus will allow you to set variables inside.
{
/your/first/command
/your/second/command
} &> /dev/null
See the bash manual on redirections for more details, options, and syntax.
You can redirect stdout to /dev/null.
yum install nano > /dev/null
Or you can redirect both stdout and stderr,
yum install nano &> /dev/null.
But if the program has a quiet option, that's even better.
A process normally has two outputs to screen: stdout (standard out), and stderr (standard error).
Normally informational messages go to sdout, and errors and alerts go to stderr.
You can turn off stdout for a command by doing
MyCommand >/dev/null
and turn off stderr by doing:
MyCommand 2>/dev/null
If you want both off, you can do:
MyCommand >/dev/null 2>&1
The 2>&1 says send stderr to the same place as stdout.
You can redirect the output to /dev/null. For more info regarding /dev/null read this link.
You can hide the output of a comand in the following ways :
echo -n "Installing nano ......"; yum install nano > /dev/null; echo " done.";
Redirect the standard output to /dev/null, but not the standard error. This will show the errors occurring during the installation, for example if yum cannot find a package.
echo -n "Installing nano ......"; yum install nano &> /dev/null; echo " done.";
While this code will not show anything in the terminal since both standard error and standard output are redirected and thus nullified to /dev/null.
>/dev/null 2>&1 will mute both stdout and stderr
yum install nano >/dev/null 2>&1
You should not use bash in this case to get rid of the output. Yum does have an option -q which suppresses the output.
You'll most certainly also want to use -y
echo "Installing nano..."
yum -y -q install nano
To see all the options for yum, use man yum.
you can also do it by assigning its output to a variable, this is particularly useful when you don't have /dev/null.
Yes, I came across a situation when I can't use /dev/null.
The solution I found was to assign the output to a variable which I will never use there after:
hide_output=$([[ -d /proc ]] && [[ mountpoint -q /proc ]] && umount -l /proc)
This:
command > /dev/null
Or this: (to suppress errors as well)
command > /dev/null 2>&1
Similar to lots of other answers but they didn't work for me with 2> being in front of dev/null.
.SILENT:
Type " .SILENT: " in the beginning of your script without colons.

How to hide command output in Bash

I want to make my Bash scripts more elegant for the end user. How do I hide the output when Bash is executing commands?
For example, when Bash executes
yum install nano
The following will show up to the user who executed the Bash:
Loaded plugins: fastestmirror
base | 3.7 kB 00:00
base/primary_db | 4.4 MB 00:03
extras | 3.4 kB 00:00
extras/primary_db | 18 kB 00:00
updates | 3.4 kB 00:00
updates/primary_db | 3.8 MB 00:02
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package nano.x86_64 0:2.0.9-7.el6 will be installed
--> Finished Dependency Resolution
Dependencies Resolved
================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
nano x86_64 2.0.9-7.el6 base 436 k
Transaction Summary
================================================================================
Install 1 Package(s)
Total download size: 436 k
Installed size: 1.5 M
Downloading Packages:
nano-2.0.9-7.el6.x86_64.rpm | 436 kB 00:00
warning: rpmts_HdrFromFdno: Header V3 RSA/SHA256 Signature, key ID c105b9de: NOKEY
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
Importing GPG key 0xC105B9DE:
Userid : CentOS-6 Key (CentOS 6 Official Signing Key) <centos-6-key#centos.org>
Package: centos-release-6-4.el6.centos.10.x86_64 (#anaconda-CentOS-201303020151.x86_64/6.4)
From : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Installing : nano-2.0.9-7.el6.x86_64 1/1
Verifying : nano-2.0.9-7.el6.x86_64 1/1
Installed:
nano.x86_64 0:2.0.9-7.el6
Complete!
Now I want to hide this from the user and instead show:
Installing nano ......
How can I accomplish this task? I will definitely help to make the script more user friendly. In case an error occurs then it should be shown to the user.
I would like to know how to show same message while a set of commands are being executed.
Use this.
{
/your/first/command
/your/second/command
} &> /dev/null
Explanation
To eliminate output from commands, you have two options:
Close the output descriptor file, which keeps it from accepting any more input. That looks like this:
your_command "Is anybody listening?" >&-
Usually, output goes either to file descriptor 1 (stdout) or 2 (stderr). If you close a file descriptor, you'll have to do so for every numbered descriptor, as &> (below) is a special BASH syntax incompatible with >&-:
/your/first/command >&- 2>&-
Be careful to note the order: >&- closes stdout, which is what you want to do; &>- redirects stdout and stderr to a file named - (hyphen), which is not what what you want to do. It'll look the same at first, but the latter creates a stray file in your working directory. It's easy to remember: >&2 redirects stdout to descriptor 2 (stderr), >&3 redirects stdout to descriptor 3, and >&- redirects stdout to a dead end (i.e. it closes stdout).
Also beware that some commands may not handle a closed file descriptor particularly well ("write error: Bad file descriptor"), which is why the better solution may be to...
Redirect output to /dev/null, which accepts all output and does nothing with it. It looks like this:
your_command "Hello?" > /dev/null
For output redirection to a file, you can direct both stdout and stderr to the same place very concisely, but only in bash:
/your/first/command &> /dev/null
Finally, to do the same for a number of commands at once, surround the whole thing in curly braces. Bash treats this as a group of commands, aggregating the output file descriptors so you can redirect all at once. If you're familiar instead with subshells using ( command1; command2; ) syntax, you'll find the braces behave almost exactly the same way, except that unless you involve them in a pipe the braces will not create a subshell and thus will allow you to set variables inside.
{
/your/first/command
/your/second/command
} &> /dev/null
See the bash manual on redirections for more details, options, and syntax.
You can redirect stdout to /dev/null.
yum install nano > /dev/null
Or you can redirect both stdout and stderr,
yum install nano &> /dev/null.
But if the program has a quiet option, that's even better.
A process normally has two outputs to screen: stdout (standard out), and stderr (standard error).
Normally informational messages go to sdout, and errors and alerts go to stderr.
You can turn off stdout for a command by doing
MyCommand >/dev/null
and turn off stderr by doing:
MyCommand 2>/dev/null
If you want both off, you can do:
MyCommand >/dev/null 2>&1
The 2>&1 says send stderr to the same place as stdout.
You can redirect the output to /dev/null. For more info regarding /dev/null read this link.
You can hide the output of a comand in the following ways :
echo -n "Installing nano ......"; yum install nano > /dev/null; echo " done.";
Redirect the standard output to /dev/null, but not the standard error. This will show the errors occurring during the installation, for example if yum cannot find a package.
echo -n "Installing nano ......"; yum install nano &> /dev/null; echo " done.";
While this code will not show anything in the terminal since both standard error and standard output are redirected and thus nullified to /dev/null.
>/dev/null 2>&1 will mute both stdout and stderr
yum install nano >/dev/null 2>&1
You should not use bash in this case to get rid of the output. Yum does have an option -q which suppresses the output.
You'll most certainly also want to use -y
echo "Installing nano..."
yum -y -q install nano
To see all the options for yum, use man yum.
you can also do it by assigning its output to a variable, this is particularly useful when you don't have /dev/null.
Yes, I came across a situation when I can't use /dev/null.
The solution I found was to assign the output to a variable which I will never use there after:
hide_output=$([[ -d /proc ]] && [[ mountpoint -q /proc ]] && umount -l /proc)
This:
command > /dev/null
Or this: (to suppress errors as well)
command > /dev/null 2>&1
Similar to lots of other answers but they didn't work for me with 2> being in front of dev/null.
.SILENT:
Type " .SILENT: " in the beginning of your script without colons.

equivalent of pipefail in dash shell

Is there some similar option in dash shell corresponding to pipefail in bash?
Or any other way of getting a non-zero status if one of the commands in pipe fail (but not exiting on it which set -e would).
To make it clearer, here is an example of what I want to achieve:
In a sample debugging makefile, my rule looks like this:
set -o pipefail; gcc -Wall $$f.c -o $$f 2>&1 | tee err; if [ $$? -ne 0 ]; then vim -o $$f.c err; ./$$f; fi;
Basically it runs opens the error file and source file on error and runs the programs when there is no error. Saves me some typing. Above snippet works well on bash but my newer Ubunty system uses dash which doesn't seem to support pipefail option.
I basically want a FAILURE status if the first part of the below group of commands fail:
gcc -Wall $$f.c -o $$f 2>&1 | tee err
so that I can use that for the if statement.
Are there any alternate ways of achieving it?
Thanks!
I ran into this same issue and the bash options of set -o pipefail and ${PIPESTATUS[0]} both failed in the dash shell (/bin/sh) on the docker image I'm using. I'd rather not modify the image or install another package, but the good news is that using a named pipe worked perfectly for me =)
mkfifo named_pipe
tee err < named_pipe &
gcc -Wall $$f.c -o $$f > named_pipe 2>&1
echo $?
See this answer for where I found the info: https://stackoverflow.com/a/1221844/431296
The Q.'s sample problem requires:
I basically want a FAILURE status if the first part of the ... group of commands fail:
Install moreutils, and try the mispipe util, which returns the exit status of the first command in a pipe:
sudo apt install moreutils
Then:
if mispipe "gcc -Wall $$f.c -o $$f 2>&1" "tee err" ; then \
./$$f
else
vim -o $$f.c err
fi
While 'mispipe' does the job here, it is not an exact duplicate of the bash shell's pipefail; from man mispipe:
Note that some shells, notably bash, do offer a
pipefail option, however, that option does not
behave the same since it makes a failure of any
command in the pipeline be returned, not just the
exit status of the first.

Resources