Bash script function call error - linux

I am writing my first Bash script and am running into a syntax issue with a function call.
Specifically, I want to invoke my script like so:
sh myscript.sh -d=<abc>
Where <abc> is the name of a specific directory inside of a fixed parent directory (~/app/dropzone). If the child <abc> directory doesn't exist, I want the script to create it before going to that directory. If the user doesn't invoke the script with a -d argument at all, I want the script to exist with a simple usage message. Here's my best attempt at the script so far:
#!/bin/bash
dropzone="~/app/dropzone"
# If the directory the script user specified exists, overwrite dropzone value with full path
# to directory. If the directory doesn't exist, first create it. If user failed to specify
# -d=<someDirName>, exit the script with a usage statement.
validate_args() {
args=$(getopt d: "$*")
set -- $args
dir=$2
if [ "$dir" ]
then
if [ ! -d "${dropzone}/targets/$dir" ]
then
mkdir ${dropzone}/targets/$dir
fi
dropzone=${dropzone}/targets/$dir
else
usage
fi
}
usage() {
echo "Usage: $0" >&2
exit 1
}
# Validate script arguments.
validate_args $1
# Go to the dropzone directory.
cd dropzone
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.
When I try running this I get the following error:
myUser#myMachine:~/app/scripts$ sh myscript.sh -dyoyo
mkdir: cannot create directory `/home/myUser/app/dropzone/targets/yoyo': No such file or directory
myscript.sh: 33: cd: can't cd to dropzone
Arrived at dropzone /home/myUser/app/dropzone/targets/yoyo.
Where am I going wrong, and is my general approach even correct? Thanks in advance!

Move the function definitions to the top of the script (below the hash-bang). bash is objecting to the undefined (at that point) call to validate_args. usage definition should precede the definition of validate_args.
There also should be spacing in the if tests "[ " and " ]".
if [ -d "$dropzone/targets/$1" ]
The getopt test for option d should be-:
if [ "$(getopt d "$1")" ]
Here is a version of validate_args that works for me.
I also had to change the drop zone as on my shell ~ wouldn't expand in mkdir command.
dropzone="/home/suspectus/app/dropzone"
validate_args() {
args=$(getopt d: "$*")
set -- $args
dir=$2
if [ "$dir" ]
then
if [ ! -d "${dropzone}/targets/$dir" ]
then
mkdir ${dropzone}/targets/$dir
fi
dropzone=${dropzone}/targets/$dir
else
usage
fi
}
To pass in all args use $* as parameter -:
validate_args $*
And finally call the script like this for getopt to parse correctly-:
myscript.sh -d dir_name

When invoked, a function is indistinguishable from a command — so you don't use parentheses:
validate_args($1) # Wrong
validate_args $1 # Right
Additionally, as suspectus points out in his answer, functions must be defined before they are invoked. You can see this with the script:
usage
usage()
{
echo "Usage: $0" >&2
exit 1
}
which will report usage: command not found assuming you don't have a command or function called usage available. Place the invocation after the function definition and it will work fine.
Your chosen interface is not the standard Unix calling convention for commands. You'd normally use:
dropzone -d subdir
rather than
dropzone -d=subdir
However, we can handle your chosen interface (but not using getopts, the built-in command interpreter, and maybe not using GNU getopt either, and certainly not using getopt as you tried to do so). Here's workable code supporting -d=subdir:
#!/bin/bash
dropzone="$HOME/app/dropzone/targets"
validate_args()
{
case "$1" in
(-d=*) dropzone="$dropzone/${1#-d=}"; mkdir -p $dropzone;;
(*) usage;;
esac
}
usage()
{
echo "Usage: $0 -d=dropzone" >&2
exit 1
}
# Validate script arguments.
validate_args $1
# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.
Note the cautious approach with the cd $dropzone || exit 1; if the cd fails, you definitely do not want to continue in the wrong directory.
Using the getopts built-in command interpreter:
#!/bin/bash
dropzone="$HOME/app/dropzone/targets"
usage()
{
echo "Usage: $0 -d dropzone" >&2
exit 1
}
while getopts d: opt
do
case "$opt" in
(d) dropzone="$dropzone/$OPTARG"; mkdir -p $dropzone;;
(*) usage;;
esac
done
shift $(($OPTIND - 1))
# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.

Related

Using ls command result in a loop

I want to use the result of ls command in a loop to check if for example the first line is a directory, second etc.
For example I have this folder that contains one directory the script should display:
18_05_2018 is directory
enter image description here
Create a file named is_file_or_directory.sh containing:
cd "$1" || echo "Please specify a path" && exit
for i in *; do
if [[ -d $i ]]; then
echo "$i is a directory"
elif [[ -f $i ]]; then
echo "$i is a file"
else
echo "$i is not valid"
exit 1
fi
done
Make that file executable with:
sudo chmod +x is_file_or_directory.sh
Run the script specifying as a parameter the path that you want to analyze:
./is_file_or_directory.sh /root/scripts/
Output:
jeeves ~/scripts/stack # ./is_file_or_dir.sh /root/scripts/
databe.py is a file
is_file_or_dir.sh is a file
mysql_flask.py is a file
test is a directory
Here's a more detailed explanation of what is happening under the hood. The variable $1 is, in Bash, the first parameter sent to the script. In our case it is the path where the script will perform its actions. Then we use the variable $i in the loop.
$i content will be every file / folder name in the path $1. With -d and -f we check if $i is a file or a folder.

Newbie: Script to change directories

The following is a snippet of a larger script I'm attempting. I just want this part to recognize the argument is a directory and then cd to that directory: i.e ./larj /etc.
#!/bin/ksh
# Filename: larj.sh
if [ $# -gt 1 ]; then
echo "0 or 1 arguments allowed."
exit
fi
if [ -f "$1" ]; then
echo "directory only."
exit
else
if [ -d "$1" ]; then
cd $1
fi
fi
When I run the script with /etc as the argument, it appears nothing happens; it stays in the same directory with no error.
Anyone have any suggestions how to get it to change directories?
Thanks
The cd is taking place within the script's shell.
When the script ends, it's shell exits, and you return to the directory before running the script. In order to change the directory you can
mkdir testdir
. ./your_script.sh testdir
At the end of the script you will be moved at directory testdir.
The problem why you cd can't work is that cd executes in the sub-shell when you execute the script as ./larj /etc. So when you execute the script, it changes the working directory of the subshell and has no impact on the current shell.
So you can execute it as . ./larj /etc.
Refer to Why doesn't “cd” work in a bash shell script?.

How to write a bash shell script which takes one argument (directory name)

How to write a bash shell script called 'abc' which takes one argument, the name of a directory, and adds the extension ".xyz" to all visible files in the directory that don't already have it
I have mostly written the code which changes the filenames inside the current directory but I can't get the script to accept an argument (directory name) and change the filenames of that directory
#!/bin/bash
case $# in
0) echo "No directory name provided" >&2 ; exit 1;;
1) cd "${1}" || exit $?;;
*) echo "Too many parameters provided" >&2 ; exit 1;;
esac
for filename in *
do
echo $filename | grep "\.xyz$"
if [ "$?" -ne "0" ]
then mv "$filename" "$filename.old"
fi
done
additional instructions include;
Within 'abc', use a "for" control structure to loop through all the non-hidden filenames
in the directory name in $1. Also, use command substitution
with "ls $1" instead of an ambiguous filename, or you'll descend into subdirectories.
EDIT: The top part of the question has been answered below, however the second part requires me to modify my own code according to the following instructions:
Modify the command substitution that's being used to create the loop values that will be placed into the "filename" variable. Instead of just an "ls $1", pipe the output into a "grep". The "grep" will search for all filenames that DO NOT end in ".xyz". This can easily be done with the "grep -v" option. With this approach, you can get rid of the "echo ... | grep ..." and the "if" control structure inside the loop, and simply do the rename.
How would I go about achieving this because according to my understanding, the answer below is already only searching through filenames without the .xyz extension however it is not being accepted.
Your description is a little unclear in places, so I've gone with the most obvious:
#!/bin/bash
# validate input parameters
case $# in
0) echo "No directory name provided" >&2 ; exit 1;;
1) cd "${1}" || exit $?;;
*) echo "Too many parameters provided" >&2 ; exit 1;;
esac
shopt -s extglob # Enables extended globbing
# Search for files that do not end with .xyz and rename them (needs extended globbing for 'not match')
for filename in !(*.xyz)
do
test -f && mv "${filename}" "${filename}.xyz"
done
The answer to the second part is this:
#!/bin/bash
for file in $(ls -1 "$1" | grep -v '\.old$'); do
mv "$file" "$file.old"
done
I got it from somewhere

find out if a command is included in folders of environment variable PATH

I cannot find out how to see if a command is included in folders of environment variable PATH. I tried the command:
$type -t $command
but it doesn't work.
Can anyone help me?
This should work:
if [[ $(type -p command) ]]; then
echo "Found"
else
echo "Not Found"
fi
You can use -t too (See exceptions at bottom.).
Or (only testing the exit status with type):
if type command >& /dev/null; then
echo "Found"
else
echo "Not Found"
fi
Note: See exceptions at bottom.
Another solution (using hash):
if [[ ! $(hash command 2>&1) ]]; then
echo "Found"
else
echo "Not Found"
fi
Note: See exceptions at bottom.
Exceptions:
type command
type help
hash command
hash help
type -t command
type -t help
command and help are bash built-ins, they are not in any path in PATH environment variable. So the other methods except the first one (with -p option) will Print out Found for bash built-in commands which are not in any path in environment PATH variable.
Better use the first method (with -p option) if you only want to check if it's located in the paths in PATH environment variable.
Or if you want to use type -t then change the if statement like this:
if [[ $(type -t command) == file ]]; then
Do you mean looking at your path? Similar to:
$ set | grep PATH
Oh, now I understand. Checking for an executable in the path is fairly easy. I usually use something like the following:
## test for exe in PATH or exit
exevar="$(which exe 2>/dev/null)"
[ x = x$exevar ] && { echo "'exe' not in path"; exit 1; }
## exe in path, continue
echo "exevar = $exevar"
or use type -p to eliminate the call to which
## test for exe in PATH or exit
exevar="$(type -p exe 2>/dev/null)"
[ x = x$exevar ] && { echo "'exe' not in path"; exit 1; }
## exe in path, continue
echo "exevar = $exevar"

Bash script with argument that makes file executable

I need to make a bash script that checks if the file or directory exists,then if the file does,it checks the executable permission.I need to modify the script to be able to give a file executable permissions from an argument.
Example: Console input ./exist.sh +x file_name should make the file executable.
This is the unfinished code that checks if the file/directory exists and if the file is executable or not. I need to add the chmod argument part.
#!/bin/bash
file=$1
if [ -x $file ]; then
echo "The file '$file' exists and it is exxecutable"
else
echo "The file '$file' is not executable (or does not exist)"
fi
if [ -d $file ]; then
echo "There is a directory named '$file'"
else
echo "There is no directory named '$file'"
fi
If you have optional arguments to your script, you need to check for them first.
In the case of just a couple of simple arguments, it would be simpler to check for them explicitly.
MAKEEXECUTABLE=0
while [ "${1:0:1}" = "+" ]; do
case $1 in
"+x")
MAKEEXECUTABLE=1
shift
;;
*)
echo "Unknown option '$1'"
exit
esac
done
file=$1
Then after you have determined that the file is not executable
if [ $MAKEEXECUTABLE -eq 1 ]; then
chmod +x $file
fi
Should you decide to add more complex options, you may want to use something like getops:example of how to use getopts in bash
Add chmod something like:
if [ ! -x "$file" ]; then
chmod +x $file
fi
This means if file does not have execute persmission, then add execute permission for the user.

Resources