bash passing arguments/parameters? - bash

#!/bin/bash
traverse() {
local x=$1
if [ -d $x ]
then
lst=(`ls $x`)
for((i=${#lst[#]}; --i;)); do
echo "${lst[i]}"
done
else echo "not a directory"
fi
}
traverse
I want to pass a parameter such as "/path/to/this/directory/" when executing program but only works if I'm running the program in the same directory as my bash script file and any other parameter I pass is completely ignored.
the script is supposed to take a parameter and check if it's a directory and if it's a directory then list all the files/folders in descending order. If not display error message.
What is wrong with code, thanks!

This happens because $1 in the function refers to traverse's parameters, not your script's parameters.
To run your function once with each argument, use
for arg in "$#" # "$#" is also the default, so you can drop the 'in ..'
do
traverse "$arg"
done
If you in the future want to pass all the script's parameters to a function, use
myfunc "$#"
This is just the problem at hand, though. Other problems include not quoting your variables and using command expansion of ls, lst=(`ls $x`), instead of globs, lst=( "$x"/* )

You don't need to call ls for that. You can use this code:
traverse() {
local x="$1"
if [ -d "$x" ]; then
arr=( "$x/"* )
for ((i=${#arr[#]}; i>0; i--)); do
echo "${arr[$i]}"
done
else
echo "not a directory"
fi
}

"That other guy" has the right answer. The reason it always looks at the current directory:
you invoke traverse with no arguments
the $1 in the traverse function is empty, therefore $x is empty
the test is therefore [ -d ], and when [ is given 1 argument, it returns success if the argument is not empty. Your if command always executes the "true" block and ls $x is just ls when x is empty
Use [[ ... ]] with bash: it is smarter about empty arguments. Otherwise, quote your variables:
$ x=; [ -d $x ] && echo always true || echo not a directory
always true
$ x=; [[ -d $x ]] && echo always true || echo not a directory
not a directory
$ x=; [ -d "$x" ] && echo always true || echo not a directory
not a directory

Related

Redundant use of :- in Bash?

I have this code inside a function:
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
What is the role of :- here? Isn't it redundant? To be specific, could we not write the if statement this way?
if [ "${!var}" ] && [ "${!fileVar}" ]; then
How does it help to have an empty "word" on the right side of :-?
Figured it out. The :- construct in indirect parameter expansion prevents the script from failing when run with set -u.
Here is an example:
set -u
var=x
[[ ${!var:-} ]] && echo This works
[[ ${!var} ]] && echo This should fail
echo "This should print only when run without 'set -u'"
which gives this output:
line 6: !var: unbound variable
If the same statements are run without set -u, we get:
This should print only when run without 'set -u'
However, this trick wouldn't work in case we are using direct parameter expansion. So,
set -u
[[ ${var:-} ]] && echo Does this work
still throws the error:
tt.sh: line 6: var: unbound variable

Shell script with absolute path and control errors

I was doing this little script in which the first argument must be a path to an existing directory and the second any other thing.
Each object in the path indicated in the first argument must be renamed so that the new
name is the original that was added as a prefix to the character string passed as the second argument. Example, for the string "hello", the object OBJECT1 is renamed hello.OBJECT1 and so on
Additionally, if an object with the new name is already present, a message is shown by a standard error output and the operation is not carried out continuing with the next object.
I have the following done:
#! /bin/bash
if [ "$#" != 2 ]; then
exit 1
else
echo "$2"
if [ -d "$1" ]; then
echo "directory"
for i in $(ls "$1")
do
for j in $(ls "$1")
do
echo "$i"
if [ "$j" = "$2"."$i" ]; then
exit 1
else
mv -v "$i" "$2"."$i"
echo "$2"."$i"
fi
done
done
else
echo "no"
fi
fi
I am having problems if I run the script from another file other than the one I want to do it, for example if I am in /home/pp and I want the changes to be made in /home/pp/rr, since that is the only way It does in the current.
I tried to change the ls to catch the whole route with
ls -R | sed "s;^;pwd;" but the route catches me badly.
Using find you can't because it puts me in front of the path and doesn't leave the file
Then another question, to verify that that object that is going to create new is not inside, when doing it with two for I get bash errors for all files and not just for coincidences
I'm starting with this scripting, so it has to be a very simple solution thing
An obvious answer to your question would be to put a cd "$2 in the script to make it work. However, there are some opportunities in this script for improvement.
#! /bin/bash
if [ "$#" != 2 ]; then
You might put an error message here, for example, echo "Usage: $0 dir prefix" or even a more elaborate help text.
exit 1
else
echo $2
Please quote, as in echo "$2".
if [ -d $1 ]; then
Here, the quotes are important. Suppose that your directory name has a space in it; then this if would fail with bash: [: a: binary operator expected. So, put quotes around the $1: if [ -d "$1" ]; then
echo "directory"
This is where you could insert the cd "$1".
for i in $(ls $1)
do
It is almost always a bad idea to parse the output of ls. Once again, this for-loop will fail if a file name has a space in it. A possible improvement would be for i in "$1"/* ; do.
for j in $(ls $1)
do
echo $i
if [ $j = $2.$i ]; then
exit 1
else
The logic of this section seems to be: if a file with the prefix exists, then exit instead of overwriting. It is always a good idea to tell why the script fails; an echo before the exit 1 will be helpful.
The question is why you use the second loop? a simple if [ -f "$2.$i ] ; then would do the same, but without the second loop. And it will therefore be faster.
mv -v $i $2.$i
echo $2.$i
Once again: use quotes!
fi
done
done
else
echo "no"
fi
fi
So, with all the remarks, you should be able to improve your script. As tripleee said in his comment, running shellcheck would have provided you with most of the comment above. But he also mentioned basename, which would be useful here.
With all that, this is how I would do it. Some changes you will probably only appreciate in a few months time when you need some changes to the script and try to remember what the logic was that you had in the past.
#!/bin/bash
if [ "$#" != 2 ]; then
echo "Usage: $0 directory prefix" >&2
echo "Put a prefix to all the files in a directory." >&2
exit 1
else
directory="$1"
prefix="$2"
if [ -d "$directory" ]; then
for f in "$directory"/* ; do
base=$(basename "$f")
if [ -f "Sdirectory/$prefix.$base" ] ; then
echo "This would overwrite $prefix.$base; exiting" >&2
exit 1
else
mv -v "$directory/$base" "$directory/$prefix.$base"
fi
done
else
echo "$directory is not a directory" >&2
fi
fi

bash: if statement with multiple checks

In my bash script I need to check if the first CLI is defined and the second one is an existing file
Here is what I have:
if [!$2] && [! -f $1 ]; then
....
fi
So $2 should exist (string) and $1 should be the existing file on the filesystem!
Any suggestions ?
If by suggestions you mean what do I need to make it work, then what you need to do is to add spaces around brackets. Also it is good to quote the variables:
if [ -n "$2" ] && [ ! -f "$1" ]; then
...
fi
From man test:
-n STRING
the length of STRING is nonzero

File is found even though folder is empty using bash - test whether a directory is empty

I want to see if files exist in a certain folder and if they exist run certain event else skip this. Bash see files in my empty folder.
I tried a few different ways and all show that a file exits.
$ [ -f ] && echo "Found" || echo "Not found"
Found
$ [ -r ] && echo "Found" || echo "Not found"
Found
$ [ -e ] && echo "Found" || echo "Not found"
Found
Whereas it does not:
$ ls -lrt
total 0
What am I missing here?
My actual code is:
get_last_parsed_file_time ()
{
if [ -s "$DATA_DIR$EPG_XML_FILES" ]
then
NEWEST_FILE=$( ls -tr | tail -1 )
LAST_PARSED_TIME=$( stat -c %Y $NEWEST_FILE )
else
NO_FILE=1
fi
}
[ -f filename ] checks if filename is a file that exists.
[ str ] checks whether str is non-empty.
In your case, str is -f, but it still just checks whether string "-f" is nonempty, which it obviously isn't (it's two characters, a dash and an "f").
This is especially puzzling in the case of [ -f $filename ]. When $filename is empty and unquoted, the command will become [ -f ] and will be true. Always quote your variables.
that other guy's answer explains why [ -f ] doesn't work.
More generally, it is important to note that you cannot pass globs (filename patterns such as * to a bash file-test operator such as -f. The file-test operators expect a single, literal filename or file path (either as a string literal or as a string stored in a variable).
[[ -f '/path/to/file' ]] # correct
[[ -f /path/to/* ]] # WRONG
Here's a snippet that tests whether a folder is empty, i.e. whether it contains ANY items - whether files, hidden files, or subfolders:
folder='/path/to/folder'
[[ -n $(ls -A "$folder" | head -n 1) ]] && echo "NON-empty" || echo "EMPTY"
To only consider non-hidden items, remove -A from the ls command.
To quietly ignore the case where the input folder doesn't exist or is not accessible, append 2> /dev/null to the ls command.

Using the value of a variable in the name of an argument

I'm writing a bash script which will input arguments, the command would look like this:
command -a -b -c file -d -e
I would like to detect a specific argument (-b) with its specific location ($1, $2, $3)
#! /bin/bash
counter=0
while [ counter -lt $# ]
do
if [ $($counter) == "-b" ]
then
found=$counter
fi
let counter+=1
done
The problem rises in $($counter). Is there a way to use $counter to call the value of an argument? For instance if counter=2, I would like to call the value of argument $2. $($counter) doesn't work.
You can accomplish this without getopts (which is still recommended, though) by reworking your loop.
counter=1
for i in "$#"; do
if [[ $i == -b ]]; then
break
fi
((counter+=1))
done
Simply iterate over the arguments directly, rather than iterating over the argument positions.
bash also does allow indirect parameter expansion, using the following syntax:
#! /bin/bash
counter=0
while [ counter -lt $# ]
do
if [ ${!counter} = "-b" ] # ${!x} uses the value of x as the parameter name
then
found=$counter
fi
let counter+=1
done

Resources