Why doesn't my script work on FreeBSD, even though it seems to work on Linux? It's as if FreeBSD ignores "if" - bash

I am trying to write a portable installation script for building the compiler for my programming language. You can see the script here:
mkdir ArithmeticExpressionCompiler
cd ArithmeticExpressionCompiler
if command -v wget &> /dev/null
then
wget https://flatassembler.github.io/Duktape.zip
else
curl -o Duktape.zip https://flatassembler.github.io/Duktape.zip
fi
unzip Duktape.zip
if command -v gcc &> /dev/null
then
gcc -o aec aec.c duktape.c -lm # The linker that comes with recent versions of Debian Linux insists that "-lm" is put AFTER the source files, or else it outputs some confusing error message.
else
clang -o aec aec.c duktape.c -lm
fi
./aec analogClock.aec
if command -v gcc &> /dev/null
then
gcc -o analogClock analogClock.s -m32
else
clang -o analogClock analogClock.s -m32
fi
./analogClock
However, when I run it on FreeBSD, it complains that wget is not found. But the script checks whether wget exists before calling it. wget is not supposed to be called on FreeBSD. Now, I know FreeBSD uses sh rather than bash, and I suppose my script is not actually POSIX-compliant. So, what am I doing wrong?

From the POSIX Spec:
If a command is terminated by the control operator ( '&'
), the shell shall execute the command asynchronously in a subshell.
This means that the shell shall not wait for the command to finish
before executing the next command.
In posix &> is not supported by posix instead it will see & as a background command indicator causing your command to be run asynchronously with the next part > /dev/null which is seen as a seperate command. This is basically if you were to run:
command -v wget & > /dev/null
Instead you have to redirect another way:
command -v wget >/dev/null 2>&1

Related

tcsh: How to find out if shell was started with -c flag?

tcsh, as with other shells, accepts the -c flag to execute a set of commands from the command-line args (instead of from a script) upon running the shell, such as:
tcsh -c 'mkdir /tmp/some-dir; tar -C /tmp/some-dir -xvf a-tarball.tar'
Is there a way to query the interpreter's state to detect that -c flag? Remember that this flag is passed on to tcsh, NOT to the commands that were fed to the interpreter via -c.
Background: I found a few days ago that tcsh -c "COMMANDS..." still invokes additional rc files (in particular, .cshrc) upon starting up. I have some commands in .cshrc that I do NOT want run when tcsh -c is invoked (as opposed to interactive tcsh).

Unable to capture command exit code in makefile

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

Why can't I redirect stderr from within a bash -c command line?

I'm trying to log the time for the execution of a command, so I'm doing that by using the builtin time command in bash. I also wish to redirect the stderr and stdout to a logfile at the same time. However, it doesn't seem to be working as the stderr just spills out onto my terminal.
Here is the command:
rm -rf doxygen
mkdir doxygen
bash -c 'time "/cygdrive/d/Program Files/doxygen/bin/doxygen.exe" Doxyfile > doxygen/doxygen.log 1>&2' genfile > doxygen/time 1>&2 &
What am I doing wrong here?
You are using 1>&2 instead of 2>&1.
With the lengths of names reduced, you're trying to run:
bash -c 'time doxygen Doxyfile > doxygen.log 1>&2' genfile > doxygen.time 1>&2 &
The > doxygen.log sends standard output to the file; the 1>&2 then changes your mind and sends standard output to the same place that standard error is going. Similarly with the outer pair of redirections.
If you used:
bash -c 'time doxygen Doxyfile > doxygen.log 2>&1' genfile > doxygen.time 2>&1 &
then you send standard error to the same place that standard output goes — twice.
Incidentally, do you realize that the genfile serves as the $0 for the script run by bash -c '…'? I'm not convinced it is needed in your script. To see this, try:
bash -c 'echo 0=$0; echo 1=$1; echo 2=$2' genfile jarre oxygene
When run, this produces:
0=genfile
1=jarre
2=oxygene

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.

using time command in bash script and redirect the output

I'm trying to write a script that does
x =$ncore
numactrl -C $x ( time -p $exe ) > out.txt 2>&1
on the terminal ( time $ exe ) > out.txt 2>&1 worked as i wanted to (out.txt containing output of time and executable )
i'm using red hat 6.2 and time is not GNU version( i'm assuming from the fact that -a -o options don't work)
i want out.txt to have output from the executable and at the end have output from the time command.
the bash script is giving me problems with having ( so i used ( time -p $exe ) and now numactl sees ( as the executable.
is there a way to use numactl and time command together and have the output i want ?
If numactrl wants a command, but you want to use some shell features, just give it the shell as a command:
numactrl -C $x bash -c "( time -p $exe ) > out.txt 2>&1"
When you runtime -p $exe from a bash prompt or within a bash -c, you're using the bash builtin version of time. The one with the -o option is an external command, so to use it from bash you have to specify command time or /bin/time or /usr/bin/time.
If you run numactrl -C $x time ... then it probably runs the external command, so -o should work in that case, but if not then you always have the bash -c method.
Note that the output format is different between the various versions of time. The GNU coreutils version prints more information than the bash builtin version.

Resources