Saving the arguments in different variables passed to a shell script - linux

I need to save two command line arguments in two different variables and rest all in third variable.
I am using following code
while [ $# -ge 2 ] ; do
DirFrom=$1
Old_Ver=`basename $1`
shift
DirTo=$1
shift
pdct_code=$#
shift
done
This code is failing if I send more than three arguments . Please suggest how can I save 3rd 4th and so on variable in pdct_code variable.

You're not entering the loop when you have more than two arguments. You can bump the argument limit like so:
while [ $# -ge 3 ]; do
:
done
or better yet just parse your arguments without looping at all. For example:
DirFrom="$1"
Old_Ver=`basename "$1"`
DirTo="$2"
pdct_code="$*"

No loop or shifting is needed. Note that pdct_code may need to be an array, to preserve the exact arguments passed to your script.
if [ $# -ge 2 ]; then
DirFrom=$1
Old_Ver=$(basename "$1")
DirTo=$2
pdct_code="${#:3}"
# pdct_code=( "${#:3}" )
done

Related

Nothing happens on bourne shell script [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I coded code that print decimals in 1 to 100.
and I executed this code, but Nothing happens...
I want to know what's the problem in my code
Please understand with my not good English
enter image description here
#!/bin/sh
i=2
while [ $i -le 100]
do
c=0
j=1
while [ $j -lt $i ]
do
if [ $(( $i % $j )) -eq 0 ]
then
c=$(( $c+1 ))
fi
j=$(( $j+1 ))
if [ $c -eq 0 ]
then
echo i
fi
done
i=$(( $i+1 ))
done
As shown by StephaneVeyret, the shebang shouldn't have spaces .
The purpose of the script seems to be printing numbers sequentially. If that is the case, you might want to take a look at a simple code like this :
#!/bin/bash
for i in {1..100}
do
printf "$i\n"
done
There should not be space in your shebang, between ! and /bin/sh. The first line of your script should be:
#!/bin/sh
(instead of #! /bin/sh)
Also, please note that if you really want to use Bourne shell, it should actually be:
#!/bin/bash
EDIT1: more problems:
As you want to use bash, you'd better use double brackets in your conditional statement, i.e.:
if [[ $j -le $i ]]
because they are more “powerful” in bash.
For this particular expression, you could also do:
if (( j <= i ))
Now, be careful to put spaces after [[ and (( and before ]] and )), and around operators:
$j=$(( $i + 1 ))
There seem to also be algo problems, but I don't have time to re-create your script and will check them only if I can copy-paste on my computer. Can you please paste an updated version of your script in your question? That would be easier for us than working on an image.
EDIT2: re your last post:
Line 4: there is a missing space before the closing bracket:
while [ $i -le 100 ]
Now, if you want to know what the script is doing, you can add the following command, for example, just after the shebang:
set -x
There are a few technical problems with your code, and also (if I understand what you are trying to do correctly) some logical errors in the design.
I'll share a few comments about how I looked at this. First thing to note is that the code you show is inconsistently indented, which means it is hard to see the logical structure of the code by simply looking at it. I loaded this into my usual editor (VSCode) which has a handy "Format Document" feature (many other code editors have similar auto-format features), and that produced the following:
#!/bin/sh
i=2
while [ $i -le 100]; do
c=0
j=1
while [ $j -lt $i ]; do
if [ $(($i % $j)) -eq 0 ]; then
c=$(($c + 1))
fi
j=$(($j + 1))
if [ $c -eq 0 ]; then
echo i
fi
done
i=$(($i + 1))
done
Note how this properly shows the nesting of statements within the inner while loop, in contrast to the original code. Even if you don't have access to a fancy editor, it is worthwhile spending the time to ensure that your code is manually indented correctly. It will save you a lot of time later in understanding and debugging the code.
Note also that the echo i line towards the end of the code should be echo $i if you want to print the value of the i variable.
When I try to run this code, it actually gives me an error telling me there's a missing ] in line 4. You need a space after the 100 here (as also pointed out by Stéphane Veyret):
while [ $i -le 100 ]; do
Next up, I added extra echo lines at various points in the code just to debug it. For example, the first thing I tried was to add the following line just before the final done line (the last line of the code)
echo "i=" $i
When I run the code I see this message printed multiple times, which tells me that the loop is running and that i is getting incremented from 2 to 100 as expected. I then added further echo lines at places within the while loop to check the values of other variables too.
These investigations highlighted a problem in your logic in the line
if [ $(($i % $j)) -eq 0 ]; then
The variable j is set to 1 at the start of each pass of the outer while loop. Any number modulus 1 is equal to zero, so this test will always be true when j is 1. This means that c will always be incremented the first time this code is encountered in each pass of the outer while loop (c=$(($c + 1))). And that means that the following test (if [ $c -eq 0 ]; then) will never be true.
I hope that gives you some ideas of what the problems are and how to go about further testing and debugging the code to make it do what you want.

Strange behavior with parameter expansion in program arguments

I'm trying to conditionally pass an argument to a bash script only if it has been set in the calling script and I've noticed some odd behavior.
I'm using parameter expansion to facilitate this, outputting an option only if the corresponding variable is set. The aim is to pass an argument from a 'parent' script to a 'child' script.
Consider the following example:
The calling script:
#!/bin/bash
# 1.sh
ONE="TEST_ONE"
TWO="TEST_TWO"
./2.sh \
--one "${ONE}" \
"${TWO:+"--two ${TWO}"}" \
--other
and the called script:
#!/bin/bash
# 2.sh
while [[ $# -gt 0 ]]; do
key="${1}"
case $key in
-o|--one)
ONE="${2}"
echo "ONE: ${ONE}"
shift
shift
;;
-t|--two)
TWO="${2}"
echo "TWO: ${TWO}"
shift
shift
;;
-f|--other)
OTHER=1
echo "OTHER: ${OTHER}"
shift
;;
*)
echo "UNRECOGNISED: ${1}"
shift
;;
esac
done
output:
ONE: TEST_ONE
UNRECOGNISED: --two TEST_TWO
OTHER: 1
Observe the behavior of the option '--two', which will be unrecognised. It looks like it is being expanded correctly, but is not recognised as being two distinct strings.
Can anyone explain why this is happening? I've seen it written in one source that it will not work with positional parameter arguments, but I'm still not understanding why this behaves as it does.
It is because when you pass $2 as a result of parameter expansion from 1.sh you are quoting it in a way that --two TEST_TWO is evaluated as one single argument, so that the number of arguments in 2.sh result in 4 instead of 5
But that said, using your $2 as ${TWO:+--two ${TWO}} would solve the problem, but that would word-split the content of $2 if it contains spaces. You need to use arrays.
As a much more recommended and fail-proof approach use arrays as below on 1.sh as
argsList=(--one "${ONE}" ${TWO:+--two "${TWO}"} --other)
and pass it along as
./2.sh "${argsList[#]}"
or if you are familiar with how quoting rules work (how and when to quote to prevent word-splitting from happening) use it directly on the command line as below. This would ensure that the contents variables ONE and TWO are preserved even if they have spaces.
./2.sh \
--one "${ONE}" \
${TWO:+--two "${TWO}"} \
--other
As a few recommended guidelines
Always use lower-case variable names for user defined variables to not confuse them with the environment variables maintained by the shell itself.
Use getopts() for more robust argument flags parsing

How to validate the number of arguments (mandatory and optional) in a shell script?

I am having trouble validating my script arguments. I am trying to achieve 5 mandatory arguments and 2 optional arguments to my script. Here is what I have tried so far.
#!/bin/sh
if [ $# -lt 5 ] || [ $# -gt 7 ]
then
echo "Please supply 5 or 6 or 7 parameters. Aborting."
exit 1
fi
echo MP1 = "$1"
echo MP2 = "$2"
echo MP3 = "$3"
echo MP4 = "$4"
echo MP5 = "$5"
while getopts c:a: optionalargs
do
case $optionalargs in
c)copt=$OPTARG;;
a)aopt=$OPTARG;;
*)echo "Invalid arg";;
esac
done
if [ ! -z "$copt" ]
then
export CHAR_SET=$copt
fi
if [ ! -z "$aopt" ]
then
export ADDITIONAL_FLAGS=$aopt
fi
shift $((OPTIND -1))
echo OP_C = "${CHAR_SET}"
echo OP_A = "${ADDITIONAL_FLAGS}"
The problem is validating the total number of arguments to this script as I can have a total of 5 or 7 arguments. Providing -a additional -c character is treated as 9 arguments.
./a.sh 1 2 3 4 5 -a additional -c character
Please supply 5 or 6 or 7 parameters. Aborting.
I am open to designs with no - as long as I am able to have both mandatory and optional parameters.
How to get this properly validated?
First: the shell doesn't know anything about optional vs. mandatory arguments, doesn't treat arguments that start with "-" specially, anything like that. It just has a list of "words", and it's up to your script to figure out what they mean. The getopts command can help with parsing the arguments, but it handles a fairly limited syntax:
Options start with a single dash, and are a single letter (maybe with an argument after that). For options that don't take arguments, you can stack multiple options on a single dash (e.g. ls -la).
After all the options, there can be a number of positional parameters (the meaning of these is defined -- as the name implies -- by their position in the list, e.g. first, second, etc).
There are a number of syntax extensions (mostly GNU conventions) that getopts does not support:
Putting options after positional parameters (e.g. ls filename -l). Options must always come first.
Long options (multi-letter and/or double-dash, e.g. ls --all).
Using -- to separate the options from the positional parameters.
So if you want to use getopts-style optional arguments, you need to put them first in the argument list. And when parsing them, you need to parse and remove* them from the argument list (with shift) before you check the number of positional parameters, and use $1 etc to access the positional parameters. Your current script is running into trouble because it's trying to handle the positional parameters first, and that won't work with getopts (at least without some heavy-duty kluging).
If you need a more general argument syntax, you might be able to use getopt (note the lack of "s" in the name). Some versions of getopt support the GNU conventions, some don't. Some have other problems. IMO this is a can of worms that's best left unopened.
Another possibility is to abandon the - option syntax, and give the script 7 positional parameters where the last two can be omitted. The problem with this is that you can't omit the sixth but pass the seventh (unless you're willing to consider the sixth being blank as equivalent to omitting it). The code for this would look something like this:
if [ $# -lt 5 ] || [ $# -gt 7 ]
then
echo "Please supply 5 or 6 or 7 parameters. Aborting."
exit 1
fi
echo "MP1 = $1"
echo "MP2 = $2"
echo "MP3 = $3"
echo "MP4 = $4"
echo "MP5 = $5"
if [ -n "$6" ]; then
# This will run if a sixth argument was specified AND it wasn't blank.
export CHAR_SET=$6
echo "OP_C = ${CHAR_SET}"
fi
if [ $# -ge 7 ]; then
# This will run if a seventh argument was specified EVEN IF it was blank.
# If you want to omit this for a blank arg, use the `-n` test instead.
export ADDITIONAL_FLAGS=$7
echo "OP_A = ${ADDITIONAL_FLAGS}"
fi
...and then run the script with e.g.
./a.sh 1 2 3 4 5 character additional # Both optional args supplied
./a.sh 1 2 3 4 5 character # Only first optional arg supplied
./a.sh 1 2 3 4 5 "" additional # Only second optional arg supplied
Or, if you really want the more extended syntax and don't want to risk the vagaries of the getopt command, you can spend a lot of time and effort writing your own parsing system. IMO this is way more work than it's worth.
Every space-separated string will be interpreted as a new argument by the shell. Are you allowed to have the -a flag or -c arguments without parameters? Assuming those need to have something following them, you actually need to allow 5 or 7 or 9 arguments. Getopts is separate from the initial shell argument parsing.
Other than that, the script looks good. For quality check, I'd recommend Shellcheck

Shell Script working with multiple files [duplicate]

This question already has answers here:
How to iterate over arguments in a Bash script
(9 answers)
Closed 5 years ago.
I have this code below:
#!/bin/bash
filename=$1
file_extension=$( echo $1 | cut -d. -f2 )
directory=${filename%.*}
if [[ -z $filename ]]; then
echo "You forgot to include the file name, like this:"
echo "./convert-pdf.sh my_document.pdf"
else
if [[ $file_extension = 'pdf' ]]; then
[[ ! -d $directory ]] && mkdir $directory
convert $filename -density 300 $directory/page_%04d.jpg
else
echo "ERROR! You must use ONLY PDF files!"
fi
fi
And it is working perfectly well!
I would like to create a script which I can do something like this: ./script.sh *.pdf
How can I do it? Using asterisk.
Thank you for your time!
Firstly realize that the shell will expand *.pdf to a list of arguments. This means that your shell script will never ever see the *. Instead it will get a list of arguments.
You can use a construction like the following:
#!/bin/bash
function convert() {
local filename=$1
# do your thing here
}
if (( $# < 1 )); then
# give your error message about missing arguments
fi
while (( $# > 0 )); do
convert "$1"
shift
done
What this does is first wrap your functionality in a function called convert. Then for the main code it first checks the number of arguments passed to the script, if this is less than 1 (i.e. none) you give the error that a filename should be passed. Then you go into a while loop which is executed as long as there are arguments remaining. The first argument you pass to the convert function which does what your script already does. Then the shift operation is performed, what this does is it throws away the first argument and then shifts all the remaining arguments "left" by one place, that is what was $2 now is $1, what was $3 now is $2, etc. By doing this in the while loop until the argument list is empty you go through all the arguments.
By the way, your initial assignments have a few issues:
you can't assume that the filename has an extension, your code could match a dot in some directory path instead.
your directory assignment seems to be splitting on . instead of /
your directory assignment will contain the filename if no absolute or relative path was given, i.e. only a bare filename
...
I think you should spend a bit more time on robustness
Wrap your code in a loop. That is, instead of:
filename=$1
: code goes here
use:
for filename in "$#"; do
: put your code here
done

what did I do wrong in initializing my array with 0 ?

when I check the length of the array is always 1 even I give more parameters in the command line
for i in $*
do
echo $i
conect[$i]=0
done
echo ${#conect}
Try this:
#!/bin/bash
declare -A conect
for i in "$#"
do
echo $i
conect[$i]=0
done
echo ${#conect[#]}
Explanation:
An associative array (i.e. indexes can be non-numeric) must be declared with declare -A. You do not need this if indexes are guaranteed to be numeric.
${#foo} is the length (number of characters) of a string-valued variable; ${#conect[#]} is the length (number of elements) of an array.
As pointed out by others, "$#" is better than $*, especially when (quoted) parameters may contain spaces.
You should use an array:
for i in "$#"
$* create one single argument separated with IFS. that's why. Use $#
What is the difference between "$#" and "$*" in Bash?
Edit
Actually, as pointed out by #that_other_guy and #Ruud_Helderman (thanks to you both), what I said isn't quite right.
First thing is the Mea Culpa, as this matters isn't the full solution.
But it made me wonders so here is the correct way.
The IFS difference is a fact. But this solely matters if you quote "$*" or "$#"
for i in "$*"
do
echo $i
done
Will output every arguments on the same line whereas
for i in "$#"
do
echo $i
done
Will do it one at a time.

Resources