Printing different messages by command exit code - bash

So I have this in my .sh file:
#!/bin/bash
echo "Building Space Cubes X for Mac...."
make OS=APPLE -k
if [$? -eq 0]
then
echo "Build completed."
echo "You can find the build under (THIS_FOLDER)/bin/build."
else
echo "Build failed! Check above for error messages!"
fi
The problem is, bash prints this message I don't even understand:
./build-mac.sh: line 7: [0: command not found
Any help or advice is appreciated!
I'm running on a Mac with Bash.

You need to add additional spaces on the if line around the square brackets. Change your code to:
if [ $? -eq 0 ]
The reason this is required is that [ is a command itself (a synonym on *nix for test) and you need to execute the [ command not the (non-existing) [$? command.

Might as well not use $? at all:
#!/bin/bash
echo "Building Space Cubes X for Mac...."
if make OS=APPLE -k ; then
echo "Build completed."
echo "You can find the build under (THIS_FOLDER)/bin/build."
else
echo "Build failed! Check above for error messages!"
fi

Related

Why does $# always return 0?

I'm trying to write a script that will only accept exactly one argument. I'm still learning so I don't understand what's wrong with my code. I don't understand why, even though I change the number of inputs the code just exits. (Note: I'm going to use $dir for later if then statements but I haven't included it.)
#!/bin/bash
echo -n "Specify the name of the directory"
read dir
if [ $# -ne 1 ]; then
echo "Script requires one and only one argument"
exit
fi
You can use https://www.shellcheck.net/ to double check your syntax.
$# tells you how many arguments the script was called with.
Here you have two options.
Option 1: Use arguments
#!/bin/bash
if [[ $# -ne 1 ]]
then
echo "Script requires one and only one argument"
exit 1
else
echo "ok, arg1 is $1"
fi
To call the script do: ./script.bash argument
Use [[ ]] for testing conditions (http://mywiki.wooledge.org/BashFAQ/031)
exit 1: by default when a script exists with a 0 status code, it means it worked ok. Here since it is an error, specify a non-zero value.
Option 2: Do not use arguments, ask the user for a value.
Note: this version does not use arguments at all.
#!/bin/bash
read -r -p "Specify the name of the directory: " dir
if [[ ! -d "$dir" ]]
then
echo "Error, directory $dir does not exist."
exit 1
else
echo "ok, directory $dir exists."
fi
To call the script do: ./script.bash without any arguments.
You should research bash tutorials to learn how to use arguments.

Testing server setup bash scripts

I'm just learning to write bash scripts.
I'm writing a script to setup a new server.
How should I go about testing the script.
i.e.
I use apt install for certain packages like apache, php etc. and then a couple of lines down there is an error.
I then need to fix the error and run it again but it will run all the install commands again.
The system will probably say the package is installed already, but what if there are commands which append strings to files.
If these are run again it will append the same string to the file a second time.
What is the best approach to write bash-scripts like this?
Can you do test runs which rollback everything after an error or end of the script?
Or even better to have the script continue from the line where the error occured the next time it is run?
I'm doing this on an Ubuntu 18.04 server.
it's a matter of how clear you want it to be to read it, but
[ -f .step01-done ] || your install command && touch .step01-done
[ -f .step02-done ] || your other install command && touch .step02-done
maybe a little easier to read:
if ! [ -f .step01-done ]; then
if your install command ; then
touch .step01-done
fi
fi
if ! [ -f .step02-done ]; then
if your other install command ; then
touch .step02-done
fi
fi
...or something in between.
Now, I would suggest creating a directory somewhere and maybe logging output from the commands to some file there (maybe tee it) but definitely putting all these files you are creating with touch there. That way if you start it from another directory by accident, it won't matter. You just need to make sure that apt-get or whatever you use actual returns false if it fails. It should.
You could even make a function that does it in a nice way...
#!/bin/bash
function do_cmd() {
if [ -f "$1.done" ]; then
echo "$2: skipping already completed step"
return 0
fi
echo -n "$2: "
$3 1> "$1.out" 2> "$1.err"
if $?; then
echo "ok"
touch "$1.done"
return 0
else
echo "failed"
echo -e "see \"$1.out\" and/or \"$1.err\" for details."
return 1
# could "exit 1" instead
fi
}
[ -d /root/mysetup ] || mkdir /root/mysetup
if ! [ -d /root/mysetup ]; then
echo "failed to find or create /root/mysetup directory
exit 1
fi
cd /root/mysetup
# ---------------- your steps go here -------------------
do_cmd prog1 "installing prog1" "apt-get install prog1" || exit 1
do_cmd prog2 "installing prog2" "apt-get install prog2" || exit 1
do_cmd startfoo "starting foo service" "service foo start" || exit 1
echo "all setup functions finished."
You would use:
do_cmd identifier "description" "command or function"
description
identifier: unique identifier used when files are generated:
identifier.out: standard output from command
identifier.err: standard error from command
identifier.done: created when command is successful
description: this is actually printed to the terminal when the step is being executed.
command or function: this is the actual command to run
not sure why stackoverflow forced me to format that last bit as code but w/e

Why doesn't my if statement with backticks work properly?

I am trying to make a Bash script where the user will be able to copy a file, and see if it was successfully done or not. But every time the copy is done, properly or not, the second output "copy was not done" is shown. Any idea how to solve this?
if [ `cp -i $files $destination` ];then
echo "Copy successful."
else
echo "Copy was not done"
fi
What you want is
if cp -i "$file" "$destination"; then #...
Don't forget the quotes.
You version:
if [ `cp -i $files $destination` ];then #..
will always execute the else branch.
The if statement in the shell takes a command.
If that command succeeds (returns 0, which gets assigned into $?), then the condition succeeds.
If you do if [ ... ]; then, then it's the same as
if test ... ; then because [ ] is syntactic sugar for the test command/builtin.
In your case, you're passing the result of the stdout* of the cp operation as an argument to test
The stdout of a cp operation will be empty (cp generally only outputs errors and those go to stderr). A test invocation with an empty argument list is an error. The error results in a nonzero exit status and thus you always get the else branch.
*the $() process substitution or the backtick process substitution slurp the stdout of the command they run
With back ticks you are testing the output of the cp command, not its status. You also don't need the test command (square brackets) here.
Just use:
if cp ... ; then
...
In addition to testing the output verses status as correctly pointed out in the other answer, you can make use of a compound command to do exactly what your are attempting, without requiring the full if ... then ... else ... fi syntax. For example:
cp -i "$files" "$destination" && echo "Copy successful." || echo "Copy was not done"
Which essentially does the exact same thing as the if syntax. Basically:
command && 'next cmd if 1st succeeded'
and
command || 'next cmd if 1st failed'
You are simply using command && 'next cmd if 1st succeeded' as the command in command || 'next cmd if 1st failed'. Together it is simply:
command && 'next cmd if 1st succeeded' || 'next cmd if 1st failed'
Note: make sure to always quote your variables to prevent word-splitting, and pathname expansion, etc...
Try:
cp -i $files $destination
#check return value $? if cp command was successful
if [ "$?" == "0" ];then
echo "Copy successful."
else
echo "Copy was not done"
fi

Bash script - Nested If Statement for If File Doesn't Exist

I'm trying to compile a script that will read user input, and check if the file after the y/n statement. Then it will make files executable. I think the problem with my script is conditional ordering but check it out yourself:
target=/home/user/bin/
cd $target
read -p "This will make the command executable. Are you sure? (y/n)" CONT
if [ "$CONT" == "y" ];
then
chmod +x $1
echo "File $1 is now executable."
else
if [ "$(ls -A /home/user/bin/)" ];
then
echo "File not found."
else
echo "Terminating..."
fi
fi
As I said, I need the script to scan for the file after the y/n statement is printed. The script works fine how it is but still gives the "file is now executable" even if the argument file doesn't exist (but just gives the standard system "cannot find file" message after the echo'd text).
Your script is mostly correct, you just need to check if the file exists first. Also, it's not the best practice to use cd in shell scripts and not needed here.
So re-writing it
#!/bin/bash
target="/home/user/bin/$1"
if [[ ! -f $target ]]; then
echo "File not found."
else
read -p "This will make the command executable. Are you sure? (y/n) " CONT
if [[ $CONT == "y" ]]; then
chmod +x "$target"
echo "File $1 is now executable."
else
echo "Terminating..."
fi
fi
To get an understanding:
Your script will take one argument (a name of a file).
You ask if you want to make that file executable.
If the answer is 'yes', you make the file executable.
Otherwise, you don't.
You want to verify that the file exists too?
I'm trying to understand your logic. What does this:
if [ "$(ls -A /home/user/bin/)" ];
suppose to do. The [ ... ] syntax is a test. And, it has to be one of the valid tests you see here. For example, There's a test:
-e file: True if file exists.
That mean, I can see if your file is under /home/user/bin:
target="/home/user/bin"
if [ -e "$target/$file" ] # The "-e" test for existence
then
echo "Hey! $file exists in the $target directory. I can make it executable."
else
echo "Sorry, $file is not in the $target directory. Can't touch it."
fi
Your $(ls -A /home/user/bin/) will produce a file listing. It's not a valid test like -e unless it just so happens that the first file in your listing is something like -e or -d.
Try to clarify what you want to do. I think this is something more along the lines you want:
#! /bin/bash
target="/home/user/bin"
if [ -z "$1" ] # Did the user give you a parameter
then
echo "No file name given"
exit 2
fi
# File given, see if it exists in $target directory
if [ ! -e "$target/$1" ]
then
echo "File '$target/$1' does not exist."
exit 2
fi
# File was given and exists in the $target directory
read -p"Do you want $target/$1 to be executable? (y/n)" continue
if [ "y" = "$continue" ]
then
chmod +x "$target/$1"
fi
Note how I'm using the testing, and if the testing fails, I simply exit the program. This way, I don't have to keep embedding if/then statements in if/then statements.

best way to programmatically check for a failed scp in a shell script

I have a shell script that I'm working on with this line of code that does a loop through local files (.gz) and does an scp. I want to test for a failed scp if possible. I am doing a loop so I can echo each file name to a log so I can keep track of it.
Can someone show me how to check for failed scp? or better yet, a good code example to do this? Thanks for your help.
for gzfile in $LOCALDMPDIR/*.gz
do
/usr/bin/scp -P 2222 -i $KEYFILE $gzfile foobar#$1:$TGTDIR
echo "$gzfile is done. " 2>&1
done
Use $? to access the return value of the last command. Check the man page for scp to verify, but I think a return value of zero means success. A non-zero value means some kind of failure.
use :
if [ $? -eq 0 ];
then
echo "OK"</br>
else
echo "NOK"</br>
fi
there're blank after "[" and before "]".
don't surround $? and 0 with quotes
You can check the varaible $? to see the return code of scp. If it returns non-zero then an error occurred.
You could also try to capture the error to a log:
for gzfile in $LOCALDMPDIR/*.gz
do
/usr/bin/scp -P 2222 -i $KEYFILE $gzfile foobar#$1:$TGTDIR 2>>/var/log/scperror.log \
&& echo "$gzfile is done." \
|| echo "scp error: $gzfile"
done
For the simpleminded like me out there who spent longer than normal messing with formatting errors:
scp "fromHere" hostname:"toThere"
if [ "$?" -eq "0" ];
then
echo "SUCCESS"
else
echo "FAIL"
fi

Resources