"mkdir || echo && exit" exiting even when mkdir succeeds - linux

mkdir $2 || echo "I can't create directory $2" && exit 8
Hi everyone, this is my first post here, so be kind.
I am making a script right now and this line troubles me.
The exit 8 should happen only if the directory $2 cannot be created.
After running the script and successfully creating that directory, it still exits on 8.
Am I missing something? I thought the command after " || " happens only if you get a false on the left side.
I am new to the Linux world, and as a guy with a little to medium C experience I am confused, help! (using ubuntu, bash, btw)

As #fernand0 suggested, the problem is the precedence of the || and && operators. You want it to run something like mkdir || ( echo && exit ) -- that is, run the echo && exit part if mkdir fails. But what it's actually doing is running something like ( mkdir || echo ) && exit -- that is, it runs the exit part if either the mkdir OR echo command succeeds. echo will almost always succeed, so it'll almost always exit.
So, you need to explicitly group the commands, to override this precedence. But don't use ( ), because that runs its contents in a subshell, and exit will just exit the subshell, not the main script; you can use { } instead, or use an explicit if block. Also, you don't actually want echo && exit, because that runs exit only if the echo command succeeds. echo almost always succeeds, but in the rare case that it fails I'm pretty sure you want the script to exit anyway.
When I need to do something like this in a script, I usually use this idiom:
mkdir "$2" || {
echo "I can't create directory $2" >&2
exit 8
}
(Note: as #CharlesDuffy suggested, I added double-quotes around $2 -- double-quoting variable references is almost always a good idea in case they contain any spaces, wildcards, etc. I also sent the error message to standard error (>&2) instead of standard output, which is also generally a better way to do things.)
If you want to be terser, you can put it all on one line:
mkdir "$2" || { echo "I can't create directory $2" >&2; exit 8; }
Note that the final ; (or a line break) is required before the }, or the shell thinks } is just an argument to exit. You could also go the other way and use an explicit if block:
if ! mkdir "$2"; then
echo "I can't create directory $2" >&2
exit 8
fi
This option is less clever and concise, but that's a good thing -- clever and concise is exactly what caused this problem in the first place; clear and unambiguous is much better for code.

|| and && do not have the precedence you are accustomed to in other languages. Unparenthesized, it is equivalent to (a || b) && c (strictly left-to-right), not a || (b && c) (where && has higher precedence than ||). It's rarely a good idea to chain commands together with a mix of || and &&; instead, use an if statement.
if ! mkdir "$2"; then
echo "I can't create directory $2" >&2
exit 8
fi
If you really want to use the list operators, use { ... } to group commands appropriately.
mkdir "$2" || { echo "I can't create directory $2" >&2; exit 8; }

Related

Bash script lost shebang path after instantiate function

I am writing a script with a iterative menu to run command lines. However, after create the iterative menu I got a error when I want run the commands.
The error is [COMMAND]No such file or directory linux.
#!/bin/bash
ATESTS=("TEST NAME 1" "TESTE NAME 2")
PATH=("test1.xml" "text2.xml")
menu () {
for i in ${!ATESTS[#]}; do
printf "%3d%s) %s\n" $((i+1)) "${OPT[i]:- }" "${ATESTS[i]}"
done
[[ "$msg" ]] && echo "$msg"; :
}
prompt="Check an ATEST (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
/usr/bin/clear;
[[ "$num" != *[![:digit:]]* ]] &&
(( num > 0 && num <= ${#ATESTS[#]} )) ||
{ msg="Invalid ATEST: $num"; continue; }
((num--)); msg="${ATESTS[num]} was ${OPT[num]:+un}checked"
[[ "${OPT[num]}" ]] && OPT[num]="" || OPT[num]="+"
done
for i in ${!ATESTS[#]}; do
[[ "${OPT[i]}" ]] && { printf "%s " "${ATESTS[i]}"; msg=""; }
done
echo "$msg"
for i in ${!ATESTS[#]}; do
if [[ "${OPT[i]}" ]] && [[ $PWD = /repo/$USER/program ]]; then
find . -iname ${PATH[i]} -exec cat {} \;
fi
done
I want find a *.xml file then execute with a script that already exist and belong to /usr/bin. However the find command dont execute and also the cat command in this example, getting the following error ([COMMAND]No such file or directory linux.)
if i try run one bash command before the function, the command execute without any problem, but after the function the commands fails.
I create one alias to the script for running inside /repo/$USER/program without include the path to the script.
The problem has nothing to do with the shebang or the function. The problem is that you're using the variable $PATH. This variable tells the system what directories to search for executable commands, so when you set it to an array... it's going to start looking for commands in the locations(s) specified by ${PATH[0]}, which is "test1.xml", which is not even a directory let alone one that contains all of the executables you need.
Solution: don't use the variable name PATH for anything other than the list of directories to search for commands. In fact, since this is one of a large number of all-uppercase variables that have special functions, it's best to use lowercase (or mixed-case) variables in your scripts, to avoid weird conflicts like this.
BTW, I can't tell if the rest of the script makes sense or not; your use of short-circuit booleans for conditional execution (e.g. this && that || do something) makes it really hard to follow the logic. I'd strongly recommend using if blocks for conditional execution (as you did in the for loop at the end); they make it much easier to tell what's going on.

How to check dependency in bash script

I want to check whether nodejs is installed on the system or not. I am getting this error:
Error : command not found.
How can i fix it?
#!/bin/bash
if [ nodejs -v ]; then
echo "nodejs found"
else
echo "nodejs not found"
fi
You can use the command bash builtin:
if command -v nodejs >/dev/null 2>&1 ; then
echo "nodejs found"
echo "version: $(nodejs -v)"
else
echo "nodejs not found"
fi
The name of the command is node, not nodejs
which returns the path to the command to stdout, if it exists
if [ $(which node 2>/dev/null) ]; then
echo "nodejs found"
else
echo "nodejs not found"
fi
This is not what the OP asked for (nearly 3 years ago!), but for anyone who wants to check multiple dependencies:
#!/bin/bash
echo -n "Checking dependencies... "
for name in youtube-dl yad ffmpeg
do
[[ $(which $name 2>/dev/null) ]] || { echo -en "\n$name needs to be installed. Use 'sudo apt-get install $name'";deps=1; }
done
[[ $deps -ne 1 ]] && echo "OK" || { echo -en "\nInstall the above and rerun this script\n";exit 1; }
Here's how it works. First, we print a line saying that we are checking dependencies. The second line starts a "for name in..." loop, in which we put the dependencies we want to check, in this example we will check for youtube-dl, yad and ffmpeg. The loop commences (with "do") and the next line checks for the existence of each command using the bash command "which." If the dependency is already installed, no action is taken and we skip to the next command in the loop. If it does need to be installed, a message is printed and a variable "deps" is set to 1 (deps = dependencies) and then we continue to the next command to check. After all the commands are checked, the final line checks to see if any dependencies are required by checking the deps variable. If it is not set, it appends "OK" to the line where it originally said "Checking dependencies.... " and continues (assuming this is the first part of a script). If it is set, it prints a message asking to install the dependencies and to rerun the script. It then exits the script.
The echo commands look complicated but they are necessary to give a clean output on the terminal. Here is a screenshot showing that the dependencies are not met on the first run, but they are on the second.
PS If you save this as an script, you will need to be in the same directory as the script and type ./{name_of_your_script} and it will need to be executable.
You may check the existence of a program or function by
type nodejs &>/dev/null || echo "node js not installed"
However, there is a more sophisticated explanation available here.
I was thinking about this and came up with a few versions, then went on the internet to see what others have to say and ended up here. Albeit an old thread, I'll reply with my thoughts.
First to answer the OP's original question: How can i fix it?
if node -v &>/dev/null; then
echo "nodejs found"
else
echo "nodejs not found"
fi
If you are simply checking if node works, this would do it. But it isn't a very generic way to do it.
Another way is to use command in a loop and collect the missing dependencies (in this example looking for the commands kind and kubectl).
for app in kind kubectl; do command -v "${app}" &>/dev/null || not_available+=("${app}"); done
(( ${#not_available[#]} > 0 )) && echo "Please install missing dependencies: ${not_available[*]}" 1>&2 && exit 1
Or less concisely expressed:
unset not_available # script safety, however not necessary.
for app in kind kubectl; do
if ! command -v "${app}" &>/dev/null; then
not_available+=("${app}")
fi
done
if (( ${#not_available[#]} > 0 )); then
echo "Please install missing dependencies: ${not_available[#]}" 1>&2
exit 1
fi
Then I figured I'd want a way to do the same without a loop, so came up with this:
not_installed=$(command -V kind kubectl 2>&1 | awk -F': +' '$NF == "not found" {printf "%s ", $(NF-1)}')
[[ -n ${not_installed} ]] && echo "Please install missing dependencies: ${not_installed}" 1>&2 && exit 1
The command -V can take any number of entries and posts the result back to stdout and stderr (though I redirect both to stdout for the next command to parse).
awk sets the field separator to <colon><one or more space>, expressed as : +. If the last field contains, "not found", print the second to last field, being the name of the command which is not installed.
Lastly, if the variable contains any data, then report back which dependencies that are missing to stderr and exit the script!
You can do dependency checks in a million ways, but here are a few alternatives which are more generally applicable and not too lengthy while still being easy to follow. :]
If all you want is to check to see if a command exists, use which command. It returns the patch if the command is called, and nothing if it is not found
if [ "$(which openssl)" = "" ] ;then
echo "This script requires openssl, please resolve and try again."
exit 1
fi

verifing some arguments in bash

I'm doing a verification in BASH
if [ !( ( -e $f ) || ($# -lt 2) || ( -d $f ) ) ]; then
exit 0
fi
I'm trying to see if the file existes or if it's a directory or enough args are passed via terminal. Can I do this or must it be done in another way?
I gather from your question you want to reject anything that is not an ordinary file. In that case, the test for a directory is redundant if you use -f instead of -e.
if [[ ! -f "$f" || $# -lt 2 ]]; then
exit 0
fi
By the way, if this is an error exit, you should exit with something other than 0 - 0 indicates success in most cases; exit 1 would be better.
It looks like you have at least a few problems here.
The syntax for your test will require the more modern double [[ command instead of the legacy single [ command.
You're missing a fi at the end. (I edited this one in for you, with thanks to whoever modded it up.)
You may have the sense of your test reversed, but I'm not sure. It depends on what the rest of your script looks like.

shell to find a file , execute it - exit if 'error' and continue if ' no error'

I have to write a shell script and i don't know how to go about it.
Basically i have to write a script where i'd find a file ( it could be possibly named differently). If either file exists then it must be executed, if it returns a 0 ( no error), it should continue the build, if it's not equal to 0 ( returns with error), it should exit. If either file is not found it should continue the build.
the file i have to find could be either file.1 or file.2 so it could be either named (file.1), or (file.2).
some of the conditions to make it more clear.
1) if either file exists , it should execute - if it has any errors it should exit, if no errors it should continue.
2) none could exist, if that's the case then it should continue the build.
3) both files will not be present at the same time ( additional info)
I have tried to write a script but i doubt it's even closer to what i am looking for.
if [-f /home/(file.1) or (file.2)]
then
-exec /home/(file.1) or (file.2)
if [ $! -eq 0]; then
echo "no errors continuing build"
fi
else
if [ $! -ne 0] ; then
exit ;
fi
else
echo "/home/(file.1) or (file.2) not found, continuing build"
fi
any help is much appreciated.
Thanks in advance
DOIT=""
for f in file1.sh file2.sh; do
if [ -x /home/$f ]; then DOIT="/home/$f"; break; fi
done
if [ -z "$DOIT" ]; then echo "Files not found, continuing build"; fi
if [ -n "$DOIT" ]; then $DOIT && echo "No Errors" || exit 1; fi
For those confused about my syntax, try running this:
true && echo "is true" || echo "is false"
false && echo "is true" || echo "is false"
Just putting the line
file.sh
in your script should work, if you set up your script to exit on errors.
For example, if your script was
#!/bin/bash -e
echo one
./file.sh
echo two
Then if file.sh exists and is executable it would run and your whole script would run. If not, the script would fail when it tried to execute the non-existing file.
If you want to execute one file or the other, extend the idea to the following:
#!/bin/bash -e
echo one
./file1.sh || ./file2.sh
echo two
This means if file1.sh does not exist, it will try file2.sh and if that is there it will run and your whole script will run.
This give preference to file1 of course, meaning if they both exist, then only file1 will run.

display message on command "cd production"

I wish to accomplish the following:
If I execute "cd production" on bash prompt, I should go into the directory and a message should be displayed "You are in production", so that the user gets warned.
Don't do it that way. :)
What you really want to know isn't whether the user just got into the 'production' directory via a cd command; what you really want to know is if you're modifying production data, and how you got there (cd, pushd, popd, opening a shell from a parent process in that directory already) is irrelevant.
It makes much more sense, then, to have your shell put up an obnoxious warning when you're in the production directory.
function update_prompt() {
if [[ $PWD =~ /production(/|$) ]] ; then
PS1="\u#\h \w [WARNING: PRODUCTION] $"
else
PS1="\u#\h \w $"
fi
}
PROMPT_COMMAND=update_prompt
Feel free to replace the strings in question with something much more colorful.
You can do it by executing the following in the shell context (e.g., .bashrc).
xcd() {
if [[ "$1" == "production" ]] ; then
echo Warning, you are in production.
fi
builtin cd $1
}
alias cd=xcd
This creates a function then aliases the cd command to that function. The function itself provides the warning then calls the real cd.
A better solution, however, may be to detect real paths, since the solution you've asked for will give you a false positive for "cd $HOME ; cd production" and false negative for "cd /production/x" (if /production was indeed the danger area).
I would do something like:
#!/bin/bash
export xcd_warn="/home/pax /tmp"
xcd() {
builtin cd $1
export xcd_path=$(pwd)
for i in ${xcd_warn} ; do
echo ${xcd_path}/ | grep "^${i}/"
if [[ $? -eq 0 ]] ; then
echo Warning, you are in ${i}.
fi
done
}
alias cd=xcd
which will allow you to configure the top-level danger directories as absolute paths.

Resources