BASH script accepting parameters - bash

I have to write a script that accepts 1+ source files and destination directory as arguments. I've attempted to write an error message for filenames that contain spaces, but its getting an error when I enter more than 1 argument with a space in between as well. Any help would be appreciated.. This is what I wrote so far:
if [ "$#" -eq "$(echo "$#" | wc -w)" ]
then
echo "Invalid arguments. Filenames may not contain spaces."
echo "usage: bkup file1 [file2...] bkup_directory"
exit 13
fi

You MAY want to try:
if [ $filename == "*" "*" ]; then
echo your error message here
fi
for the testing of spaces in filenames.
As for actually getting the args
$1 is the variable for the first arg and $2 for the second and so on so something like this might work:
if [ $2 == "*" ]; then
echo NO NO NO NOT TODAY!!!
fi
this would check to see if the second argument says anything at all and so if it did we can assume that it is part of the file name (THIS WOULD ONLY WORK IF THE FILENAME IS THE LAST ARGUMENT) OR:
if [ $2 == "*" ]; then
fil=$1 + \
filename=&fil + $2
fi
this would automatically change their filename into an acceptable format for the system an you would not need an error message.
but i am also new to bash so this could not be what you are looking for or I could have the right idea and all this could be complete whooey..... But if I helped then I'm glad I could.

Related

Simple bash shell program

I want to write the bash script which would accept 4 parameters: name of file, name of directory, and two strings.
If there is a mistake (if first parameter is not a file or second is not a directory) then string which is a third parameter should be printed else file should be copied to directory and string which is a fourth parameter should be printed. I don't why the compiler reports mistake in line 3 with then.
#!/bin/bash
if [-f $1]; then
if[-d $2] ; then
cp $1 / $2
echo $4
fi
done
else
echo $3
exit 1
fi
If you are having problems, paste your code in at https://www.shellcheck.net/
Fix each issue, then get the report again.
The result:
#!/bin/bash
if [ -f "$1" ]; then
if [ -d "$2" ] ; then
cp "$1" / "$2"
echo "$4"
fi
else
echo "$3"
exit 1
fi
I still think you are likely to have an issue at line 4 though, when it tries to copy the root directory into arg 2 without -r. I think what you meant was just
cp "$1" "$2"
Also, you have action for the case that someone passes a valid file as $1 but a non-directory as $2. The program will just exit silently and do nothing.

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

For loop to find out if directory exists in unix

I want to use a script that checks whether a list of directories exists or not and at the same time it should print some custom message that I am sending.
For example:
I have a script that validates if directory exists or not:
**check.sh**
for i in $*
if [ -d "$i" ]; then
echo Found <msg-i> directory.
else
echo <msg-i> directory not found.
Now I want to call this script like this:
./check.sh $DIR1 msg1 $Dir2 msg2 $Dir3 msg3
So if DIR1 doesn't exist then I want to display message as "msg1 directory not found", similarly for DIR2 I want to show "msg2 directory not found". Here msg1 and msg2 are something I want to pass as string. How to achieve this? I am using bash shell.
Try this:
while [ -n "$1" ]
do
dir="$1"
msg="$2"
if [ -d "$dir" ]; then
echo "$msg dir FOUND"
else
echo "$msg dir NOT FOUND"
fi
shift 2
done
shift <n> command simply shifts left positional parameters passed to the script of n positions.
For example if you call a script with:
./myscript 1 2 3 4
$1 is "1" and $2 is "2"
but if you shift 2 then $1 is "3" and $2 is "4".
In this way the loop consumes 2 parameters per cycle until $1 parameter is an empty string ( -n "$1").
while condition can be written more elegantly as:
while (( $# ))
obtaining the same result.
You can also check for the second parameter (while [ -n "$2" ]) but this changes the behavior when user provides an odd number of parameters:
in the first case last directory will be checked but you'll have a strange message because $msg il empty
il the second case you'll not have strange messages, but last directory will silently not be checked
Better test parameters at the beginning:
if (( $# % 2 ))
then
echo "Provide an even number of parameters"
exit 1
fi
Chepner Says:
The while condition can simply be (( $# )) (test if the number of positional parameters is non-zero).
Chaitanya Says:
Hi Chepner, thanks for providing alternate solution, can you please tell me how the while condition should actually look like in order to use $# , I tried different ways but it is not working for me.
Here's a quick sample:
while (( $# ))
do
dir=$1
msg=$2
shift 2
[...]
done
The while (( $# )) will be true as long as there are any command line arguments. Doing the shift twice removes arguments from the list. When no more arguments, the while loop ends.
#Zac has the correct answer.
One tip for the message: use a printf format string:
./check.sh dir1 "can't locate %s directory"
and in the script:
if [[ ! -d "$dir" ]]; then
printf "$msg" "$dir"

Bash - if and for statements

I am little unfamiliar with the 'if...then...fi' and the 'for' statements syntax.
Could anyone explain what the "$2/$fn" and "/etc/*release" in the code snippets below mean?...specifically on the use of the forward slash....and the asterisk...
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
and
function system_info
{
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 $i
done
uname -orp
echo "</pre>"
fi
} # end of system_info
...thx for the help...
/etc/*release : here the * will match any number of any characters, so any thing /etc/0release , /etc/asdfasdfr_release etc will be matched. Simply stated, it defined all the files in the /etc/ directory which ends with the string release.
The $2 is the 2nd commandline argument to the shell script, and $fn is some other shell variable. The "$2/$fn" after the variable substitutions will make a string, and the [ -f "$2/$fn" ] will test if the string formed after the substitution forms a path to a regular file which is specified by the -f switch. If it is a regular file then the body of if is executed.
In the for loop the loop will loop for all the files ending with the string release in the directory /etc (the path). At each iteration i will contain the next such file name, and for each iteration the first 1 line of the file is displayed with the head command by getting the file name from variable i within the body.
It is better to check the manual man bash and for if condition check man test . Here is a good resource: http://tldp.org/LDP/Bash-Beginners-Guide/html/
The forward slash is the path separator, and the * is a file glob character. $2/$fn is a path where $2 specifies the directory and $fn is the filename. /etc/*release expands to the space separated list of all the files in /etc whose name ends in "release"
Dollar sign marks variable. The "-f" operator means "file exsists".
So,
[ -f "$filename" ]
checks if there is file named the same as value contained in $filename variable.
Simmilar, if we assume that $2 = "some_folder", and $fn = "some_file", expression
[ ! -f "$2/$fn" ]
returns true if file some_folder/some_file doesn't exsist.
Now, about asterisk - it marks "zero or more of any character(s)". So, expression:
for i in /etc/*release; do
will iterate trough all folders named by that pattern, for example:
/etc/release, /etc/666release, /etc/wtf_release...
I hope this helps.

How to handle "--" in the shell script arguments?

This question has 3 parts, and each alone is easy, but combined together is not trivial (at least for me) :)
Need write a script what should take as its arguments:
one name of another command
several arguments for the command
list of files
Examples:
./my_script head -100 a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' *.txt
and so on.
Inside my script for some reason I need distinguish
what is the command
what are the arguments for the command
what are the files
so probably the most standard way write the above examples is:
./my_script head -100 -- a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' -- *.txt
Question1: Is here any better solution?
Processing in ./my_script (first attempt):
command="$1";shift
args=`echo $* | sed 's/--.*//'`
filenames=`echo $* | sed 's/.*--//'`
#... some additional processing ...
"$command" "$args" $filenames #execute the command with args and files
This solution will fail when the filenames will contain spaces and/or '--', e.g.
/some--path/to/more/idiotic file name.txt
Question2: How properly get $command its $args and $filenames for the later execution?
Question3: - how to achieve the following style of execution?
echo $filenames | $command $args #but want one filename = one line (like ls -1)
Is here nice shell solution, or need to use for example perl?
First of all, it sounds like you're trying to write a script that takes a command and a list of filenames and runs the command on each filename in turn. This can be done in one line in bash:
$ for file in a.txt b.txt ./xxx/*.txt;do head -100 "$file";done
$ for file in *.txt; do sed -n 's/xxx/aaa/' "$file";done
However, maybe I've misinterpreted your intent so let me answer your questions individually.
Instead of using "--" (which already has a different meaning), the following syntax feels more natural to me:
./my_script -c "head -100" a.txt b.txt ./xxx/*.txt
./my_script -c "sed -n 's/xxx/aaa/'" *.txt
To extract the arguments in bash, use getopts:
SCRIPT=$0
while getopts "c:" opt; do
case $opt in
c)
command=$OPTARG
;;
esac
done
shift $((OPTIND-1))
if [ -z "$command" ] || [ -z "$*" ]; then
echo "Usage: $SCRIPT -c <command> file [file..]"
exit
fi
If you want to run a command for each of the remaining arguments, it would look like this:
for target in "$#";do
eval $command \"$target\"
done
If you want to read the filenames from STDIN, it would look more like this:
while read target; do
eval $command \"$target\"
done
The $# variable, when quoted will be able to group parameters as they should be:
for parameter in "$#"
do
echo "The parameter is '$parameter'"
done
If given:
head -100 test this "File name" out
Will print
the parameter is 'head'
the parameter is '-100'
the parameter is 'test'
the parameter is 'this'
the parameter is 'File name'
the parameter is 'out'
Now, all you have to do is parse the loop out. You can use some very simple rules:
The first parameter is always the file name
The parameters that follow that start with a dash are parameters
After the "--" or once one doesn't start with a "-", the rest are all file names.
You can check to see if the first character in the parameter is a dash by using this:
if [[ "x${parameter}" == "x${parameter#-}" ]]
If you haven't seen this syntax before, it's a left filter. The # divides the two parts of the variable name. The first part is the name of the variable, and the second is the glob filter (not regular expression) to cut off. In this case, it's a single dash. As long as this statement isn't true, you know you have a parameter. BTW, the x may or may not be needed in this case. When you run a test, and you have a string with a dash in it, the test might mistake it for a parameter of the test and not the value.
Put it together would be something like this:
parameterFlag=""
for parameter in "$#" #Quotes are important!
do
if [[ "x${parameter}" == "x${parameter#-}" ]]
then
parameterFlag="Tripped!"
fi
if [[ "x${parameter}" == "x--" ]]
then
print "Parameter \"$parameter\" ends the parameter list"
parameterFlag="TRIPPED!"
fi
if [ -n $parameterFlag ]
then
print "\"$parameter\" is a file"
else
echo "The parameter \"$parameter\" is a parameter"
fi
done
Question 1
I don't think so, at least not if you need to do this for arbitrary commands.
Question 3
command=$1
shift
while [ $1 != '--' ]; do
args="$args $1"
shift
done
shift
while [ -n "$1" ]; do
echo "$1"
shift
done | $command $args
Question 2
How does that differ from question 3?

Resources