VERBOSE environment in shell script - bash

I'm trying to write a Bash script, which compiles and runs all C files in the current directory. My Bash script is the following:
#!/bin/bash
LIST="$(ls *.c)"
echo "Compile all C source files"
for f in $( ls *.c); do
#echo "C file: $f"
gcc $f -o "${f%.*}"
./"${f%.*}"
done
Now, I'm trying to define a VERBOSE environment variable. If VERBOSE environment variable is set then my Bash script should display the command that is being used to compile the source file.
How can I define such a VERBOSE environment variable in this Bash script?
When verbose is defined my output should be like,
Compiling all C source files:
gcc copyfile.c -o copyfile
---- successful ----
gcc haserror.c -o haserror
haserror.c: In function ‘main’:
haserror.c:9:10: warning: missing terminating " character
printf("Hello !\n);
^
haserror.c:9:10: error: missing terminating " character
printf("Hello!\n);
^~~~~~~~~~~~~~~~~~~
haserror.c:11:1: error: expected expression before ‘}’ token
}
^
haserror.c:11:1: error: expected ‘;’ before ‘}’ token
gcc hello.c -o hello
---- successful ----
gcc p005.c -o p002
---- successful ----
gcc p103.c -o p101
---- successful ----
gcc p102.c -o p105
---- successful ----
============
5 C source files are compiled successfully.
1 C source files have compilation error(s).
**Otherwise, when my VERBOSE not defined, my output should be like**
Compiling all C source files:
haserror.c: In function ‘main’:
haserror.c:9:10: warning: missing terminating " character
printf("Hello!\n);
^
haserror.c:9:10: error: missing terminating " character
printf("Hello!\n);
^~~~~~~~~~~~~~~~~~~
haserror.c:11:1: error: expected expression before ‘}’ token
}
^
haserror.c:11:1: error: expected ‘;’ before ‘}’ token
============
5 C source files are compiled successfully.
1 C source files have compilation error(s).

The trivially obvious shoud work.
#!/bin/bash
[ "$VERBOSE" ] && echo "$0: Compile all C source files" >&2
for f in *.c; do
[ "$VERBOSE" ] && echo "$0: C file: $f" >&2
# [ "$VERBOSE" ] && set -x
gcc $f -o "${f%.*}"
# set +x
./"${f%.*}"
done
Notice also how we avoid the useless (and slightly dangerous) use of ls and print the diagnostic output to standard error, with the script's name included in the message.
The condition is simple; [ "$VERBOSE" ] evaluates to true (returns a zero exit code) when VERBOSE is set and non-empty. You can perform arbitrarily complex actions conditionally using the shell's normal flow control statements, such as perhaps
if [ "$VERBOSE" ]; then
echo "$0: compiling $f" >&2
(set -x; gcc "$f" -o "${f.c}") &&
echo "$0: ----- $f: successful -----" >&2
else
gcc "$f" -o "${f.c}"
fi
(though I would perhaps then also refactor the compilation command into a separate function.)
A better design is to have the script accept a command-line option. Also, you should probably avoid using upper case for your private variables.

Related

Unexpected EOF in conditional construct in makefile

I have the following target in my makefile
omp: main_omp.c omp_impl.o
if [[ ! -e ../bin/ ]]; then mkdir ../bin/ fi
gcc $(CFLAGS) ... # compilation et cetera
On executing make omp in the same directory causes make to terminate with the following error
if [[ ! -e ../bin ]]; then mkdir ../bin fi
/bin/sh: 1: Syntax error: end of file unexpected (expecting "fi")
make: *** [makefile:10: omp] Error 2
Executing the if ... fi statement in the terminal works as intended. I tried different combinations of double quotes, splitting into different lines etc and nothing works.
How do I fix this problem? Why is make running into an EOF over here?
You state:
Executing the if ... fi statement in the terminal works as intended.
I doubt that. If I cut-and-paste your example, I get a continuation prompt from the shell:
if [[ ! -e ../bin/ ]]; then mkdir ../bin/ fi
>
And that is logical. Your shell (either via the prompt or via make) sees that you want to execute mkdir with two arguments ../bin and fi. The solution is of course to make sure that the shell sees the fi as the next "command". To do that, you need to add a ; before the fi.

How to derive the meaning of error produced by make?

Having this simple makefile rule:
exe:
for i in *; do [ -x "$$i" ] && echo "$$i"; done
Will output:
for i in *; do [ -x "$i" ] && echo "$i"; done
executablefile
make: *** [makefile:6: exe] Error 1
So it does, what I want, but even then, error with no other message. But not only for this particular example (which I still do not get), I would like to know, how to get some more info from bugs in makefile (is there a makefile debugger?). From makefile manual the *** is for fatal error, which ends compilation, but yet it outputs the executablefile (so it did compiled to that point). Apart from fatal error, - warnings give more info, so why do not do fatal errors as well?
explanation of this example
some advices how to debug makefile scripts
This is not an error from make, that's why there's no other information.
Make runs a shell and gives the shell your recipe to invoke. If the shell exits with success (exit code 0) then make assumes that the command it ran worked. If the shell exits with failure (any exit code other than 0), then make assumes the command it ran failed. Make doesn't know why it failed, make assumes that whatever command failed will have printed some information about why. All make knows is the exit code, so that's all make can tell you:
make: *** [makefile:6: exe] Error 1
This means that make ran the recipe for target exe at makefile line number 6, and that command exited with an error code 1 (which is not 0, hence a failure).
Why did this happen? Let's look at your shell script:
for i in *; do [ -x "$$i" ] && echo "$$i"; done
Let's suppose the last file matching * (so the last time we go through the loop) the file is not executable. That means the test of the last file [ -x "$$i" ] will fail. Since that's the last command that the shell runs before it exits, that will be the exit code of the shell, and you have a failure.
You need to be sure that the shell exits with success. One way to do that is ensure the last command the shell runs is always success; maybe something like this:
for i in *; do [ -x "$$i" ] && echo "$$i"; done; true

Detecting gcc warnings from a bash wrapper

Writing a bash script that will tell me is the gcc had Errs or warnings
i have this code
#!/bin/bash
# execute gcc command
gcc "$2".c -Wall -g -o "$2"> "$1" 2>&1
# grab exit status of gcc
ret=$?
# write appropriate message as per return status value
((ret == 0)) && echo "compile V" || echo "compile X"
# return the exit status of gcc
exit $ret
the problem is when it gets warnings its still return 0 and so i get Compile V
But without any luck.. The numbers 6 21 118 are random the rest are consts
edit
i cant use -Werror beause its for school and i must get same output as they want...
If you want to look at whether the file you redirected gcc's output to is empty, that'll tell you whether any warnings were written:
gcc "$2".c -Wall -g -o "$2" >"$1" 2>&1; rc=$?
if (( rc > 0 )) || [ -s "$1" ]; then
echo "compile X"
else
echo "compile V"
fi
This works because test -s checks whether a file exists and is non-empty -- so you know that if the file you redirected the output to is non-empty, you had at least one error or failure.
use gcc -Werror option to treat all warnings as errors.

Giving input to a shell script through command line

I have a file like this with .sh extension..
clear
echo -n "Enter file name: "
read FILE
gcc -Wall -W "$FILE" && ./a.out
echo
When I can execute this file, it asks for a .c file and when given, it compiles and gives output of the .c file.
For this, everytime I have to first execute this .sh file and then give it the .c file name when asked. Is there anyway, so that, I can just give the .c file in the command line itself, so that it takes that file and does the work...
What I mean is, if I give "./file.sh somecommand cfile.c", then it takes cfile.c as input, compiles it and gives the output...
Use '$1' variable:
clear
gcc -Wall -W $1 && ./a.out
echo
$1 means "first argument from the command line".
Alternatively, if you want to compile multiple files at once using your script, you can use $# variable, on example:
gcc -Wall -W $# && ./a.out
You will invoke your script as follows (assuming it's called 'script.sh'):
./script.sh file.c
Plase see section 3.2.5 of the following article.
If your project gets bigger, you may also want to consider using tools designated for building, like automake.
You can also have it do things either way:
if [ -n "$1" ] ; then
FILE="$1"
else
echo -n "Enter file name: "
read FILE
fi
gcc -Wall -W "$FILE" && ./a.out
This will use the command line argument if it is there, otherwise it asks for a file name.

Makefile - how to differentiate between a variable assignment and a command

I have the following code in my Makefile, but I get the error mentioning that O?=2 is not a command that sh -c can run. What am I doing wrong here?
gcc:
O?=2
#if test -z "$(DEBUG)" ; then \
g++ -O${O} *.c -o palindrome ; \
fi
I am trying to set O to 2 if the user did not provide it, and then build my palindrome executable.
Exact error:
$ make gcc
O?=2
/bin/sh: O?=2: command not found
make: *** [gcc] Error 127
$
Move it outside the command, so that it gets processed by "make" instead of the shell:
O?=2
gcc:
#if test -z "$(DEBUG)" ; then \
g++ -O${O} *.c -o palindrome ; \
fi
The line O?=2 is not an assignment, but a command executed to rebuild target gcc. This means it is fed to $(SHELL), which doesn't know a thing about it. If you want a variable assingment, put it on line by itself and not as a part of commands:
O?=2
gcc:
#if test -z "$(DEBUG)" ; then \
g++ -O${O} *.c -o palindrome ; \
fi
You can just use O = 2. When the user provides a value on the command line it will override it:
make O=1
will use -O1 for optimization.

Resources