Bash script: expansion of argument not using $# or $* - linux

Using $# you can do things to a list of files in bash. Example:
script.sh:
#!/bin/bash
list=$#
for file in $list; do _commands_; done
Then i can call this program with
~/path/to/./script dir1/{subdir1/*.dat,subdir2/*}
This argument will expand to a number of arguments that becomes $list. But now i want other arguments, say $1, $2, and this listing to be $3. So i want the expansion of dir1/{subdir1/*.dat,subdir2/*} to occur inside the script, instead of becoming many arguments. On the command line you can do this:
find dir1/{subdir1/*.dat,subdir2/*}
And get the desired output, i.e. a list if files. So I tried things like this:
arg1=$1
arg2=$2
list=$(find $3)
for file in $list; do _commands_; done
...
calling:
~/path/to/./script arg_1 arg_2 'dir1/{subdir1/*.dat,subdir2/*}'
But without success. Some help on how to make this list expand into a variable inside the script would be well appreciated!:)
EDIT: So answers below gave the solution using these commands:
arg1="$1"
arg2="$2"
shift 2
for f in "$#"; do echo "processing $f"; done;
But out of curiosity, is it still possible to pass the string dir1/{subdir1/*.dat,subdir2/*} to a find command (or whichever means to the same end) inside the script, without using $#, and obtain the list that way? This could be useful e.g. if it is preferable to have the listing as not the first or last argument, or maybe in some other cases, even if it requires escaping characters or quoting the argument.

You can have this code in your script:
arg1="$1"
arg2="$2"
shift 2
for f in "$#"; do echo "processing $f"; done;
Then call it as:
~/path/to/script arg_1 arg_2 dir1/{subdir1/*.dat,subdir2/*}
Using shift 2 will move positional parameters 2 places thus making $3 as $1 and $4 as $2 etc. You can then directly invoke $# to iterate the rest of the arguments.
As per help shift:
shift: shift [n]
Shift positional parameters.
Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N is

The shell expansion is performed by the shell, before your script is even called. That means you'll have to quote/escape the parameters. In the script, you can use eval to perform the expansion.
#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
eval "list=($#)"
for q in "${list[#]}" ; do echo "$q" ; done
$ ./a 123 456 'a{b,c}' 'd*'
ab ac d.pl docs
I don't see the point of doing the expansion inside the script in your example.
#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
list=("$#")
for q in "${list[#]}" ; do echo "$q" ; done
or just
#!/bin/bash
arg1="$1" ; shift
arg2="$2" ; shift
for q in "$#" ; do echo "$q" ; done
$ ./a 123 456 a{b,c} d*
ab
ac
d.pl
docs

Related

Using a for loop input to rename an object within the loop in bash

I am currently trying to rename an input argument by the variable "i" in the following for loop:
cd $1
num=$(echo $#)
echo $num
echo $#
echo "This is the next part where I print stuff"
for i in $(seq 2 $num)
do
echo $i
echo ${!i}
Args_array+=$(printf '${arg_%s[#]}' ${i})
echo $Args_array
arg_${i}=$(ls ${!i})
done
The output is as follows:
4
output_folder /path/to/my.tsv /path/to/my2.tsv /path/to/my3.tsv
2
/path/to/my.tsv
${arg_2[#]}
/var/spool/slurm/d/job6985121/slurm_script: line 23: arg_2=/path/to/my.tsv: No such file or directory
But it will not allow me to rename the $2, $3 arguments with "i" like this. Any help would be appreciated.
I want to pass these arguments into R and have to put them in arg_1, arg_2, etc. format.
Not sure I understand what's being attempted with Args_array so focusing solely on OP's comment: 'have to put them in arg_1, arg_2' and skipping arg_1 since OP's code doesn't appear to care about storing $1 anywhere; then again, is R not capable of processing input parameters from the command line?
One bash idea:
$ cat testme
#!/usr/bin/bash
num=$#
for ((i=2;i<=$num;i++))
do
declare args_$i=${!i}
done
for ((i=2;i<=$num;i++))
do
typeset -p args_$i
done
Taking for a test drive:
$ testme output_folder /path/to/my.tsv /path/to/my2.tsv /path/to/my3.tsv
declare -- args_2="/path/to/my.tsv"
declare -- args_3="/path/to/my2.tsv"
declare -- args_4="/path/to/my3.tsv"

how to use $* but it doesnt consider a special arguement in bash

I want to use $* in a loop but the first argument is ignored.
in other words:
sum=0
for i in $*
do
if [ $1 = "+" ]; then
sum=$(($sum+$i))
fi
done
echo $sum
To remove the first parameter, use shift.
But before you shift, you should store the value somewhere.
As the first parameter doesn't change, you can check it just once before starting the loop:
#! /bin/bash
op=$1
shift
if [ "$op" = + ] ; then
sum=0
for i in $* ; do
sum=$(($sum+$i))
done
echo $sum
fi
Note that you don't need the quotes around +. You should quote the $op in the condition, though, to prevent parsing errors (try running the script specifying an empty string "" as the first argument).
When using $((, note that you can use shorter and faster way to increment a variable:
((sum+=i))

Bash Programming how to call "$i+1"

I am writing a script that will allow me to change a char in a string from "#" to something else, if I call an argument in terminal.
eg if I write
./myprogram testText.txt -r a
the -r argument will remove all "#" from testTxt.txt and replace them with "a"
My problem is I do not know how to write "If -r is $x, $x+1 is the char I want for replacement"
This is purely a syntax problem, I'm a bash noob :P. Here is the part of code I'm trying to work with.
for i in $*
do
if [[ $i = "-r" ]]
then
$customHashChoice=$((i+1))
# ^^^^^ Problematic Line ^^^^
fi
done
Try this:
customHashChoice=($(getopt "r:" "$#" 2>/dev/null))
if [ "${customHashChoice[0]}" == "-r" ]; then
customHashChoice="${customHashChoice[1]}"
else
echo "-r option is missing. Aborting..."
exit 1
fi
Syntax: getopt optstring parameters
From manual: getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.
Here, optstring is r:. It means, that the script accepts an option -r & the option takes an argument (implied by :).
The output of getopt "r:" "$#" is as below:
-r <argument to -r option> -- <unmatched parameters>
e.g. for command-line arguments,
./myprogram testText.txt -r a
getopt "r:" "$#" returns
-r a -- testText.txt
This output is stored in array & the second element of array is used, if the first element is equal to -r.
i=1
while [ "$i" -le $# ]
do
if [[ ${!i} = "-r" ]]
then
i=$(($i + 1))
customHashChoice=${!i}
i=$(($i + 1))
continue
fi
# do something useful
i=$(($i + 1))
done
The command line arguments are numbered 1 through $#. The above loops through each of them. If first checks if the current argument is -r and, if so, sets customHashChoice.
In the above, i contains the argument number. So, $i gives the value of i. To access the i'th command line argument, one uses ${!i}.
A more standard approach
The standard way to process command line arguments in shell scripts is getopts. It can handle many options. Here is sample code that that takes an option -r and requires it to have an argument, which is assigned to the shell variable char:
while getopts r: arg ; do case $arg in
r) char="$OPTARG" ;;
:) echo "${0##*/}: Must supply an argument to $OPTARG." ; exit 1 ;;
\?) echo "Invalid option" ; exit 1 ;;
esac
done
shift $(($OPTIND - 1))
echo "I will replace # with $char in file $1"
For getopts to work, the options have to come first. So, your command line would becomes:
./myprogram -r a testText.txt
If this is not acceptable, you can roll your own custom option processor. In the long run, there is some advantage, however, to standardizing on the usual approach.
You could do something like the following:
#!/bin/bash
val=
xval=
fname=$1
while [ "$*" != "" ]; do
case $1 in
"-r") val="${2}"; shift ;;
"-x") xval="${2}"; shift ;;
esac
shift
done
echo ${fname} ${val} ${xval}
Then when you pass the command like so
./myprogram testText.txt -r a
fname will be testText.txt, and the arguments will be parsed (where the -r will pick up a); for any other values you might want to parse, you'll need variable names to assign and test against. The output would be:
testText.txt a
Hope that helps

run shell script with arguments manipulation

I need to get three arguments by test.ksh script
as the following
./test.ksh 12 34 AN
is it possible to set the argument by counter for example ?
for get_arg 1 2 3
do
my_array[get_arg]=$$get_arg
print ${my_array[get_arg]}
done
in this example I want to get three arguments from the user by loop counter "$$get_arg"
in place of $1 $2 $3
is it possible? and how ?
my_array=("$#")
for i in 0 1 2
do
echo "${my_array[$i]}"
done
This assigns all the arguments to array my_array; the loop then selects the first three arguments for echoing.
If you're sure you want the first three arguments in the array, you could use:
my_array=("$1" "$2" "$3")
If you want the 3 arguments at positions 1, 2, 3 in the array (rather than 0, 1, 2), then use:
# One or the other but not both of the two assignments
my_array=("dummy" "$#")
my_array=("dummy" "$1" "$2" "$3")
for i in 1 2 3
do
echo "${my_array[$i]}"
done
bash has a special variable
$#
which contains the arguments of the script it currently executes. I think this is what your'e looking for:
for arg in $# ; do
# code
done
Edit:
My bad ksh:
for arg;do
print $arg
done
Original Post:
Use shift to iterate through shell script parameters:
# cat test.sh
#!/bin/bash
while [ "$1" != "" ]; do
echo $1
shift
done
test run:
# ./test.sh arg1 monkey arg3
arg1
monkey
arg3
source
Even you don't need in $#, this would work the same:
#!/bin/bash
i=0
for arg; do
my_array[i]="$arg"
echo "${my_array[i]}"
(( i++ ))
done
That is,
if in words is not present, the for command executes the commands
once for each positional parameter that is set, as if in $# had been
specified.

RegEx in bash-script (for-loop)

I want to parse the arguments given to a shell script by using a for-loop. Now, assuming I have 3 arguments, something like
for i in $1 $2 $3
should do the job, but I cannot predict the number of arguments, so I wanted use an RegEx for the range and $# as the number of the last argument. I don't know how to use these RegEx' in a for-loop, I tried something like
for i in $[1-$#]
which doesn't work. The loop only runs 1 time and 1-$# is being calculated, not used as a RegEx.
Basic
A for loop by default will loop over the command-line arguments if you don't specify the in clause:
for arg; do
echo "$arg"
done
If you want to be explicit you can get all of the arguments as "$#". The above loop is equivalent to:
for arg in "$#"; do
echo "$arg"
done
From the bash man page:
Special Parameters
$# — Expands to the positional parameters, starting from one. When the expansion occurs within
double quotes, each parameter expands to a separate word. That is, "$#" is equivalent to "$1" "$2" .... If the double-quoted expansion occurs within a word, the expansion of the first
parameter is joined with the beginning part of the original word, and the expansion of the
last parameter is joined with the last part of the original word. When there are no positional parameters, "$#" and $# expand to nothing (i.e., they are removed).
Advanced
For heavy-duty argument processing, getopt + shift is the way to go. getopt will pre-process the command-line to give the user some flexibility in how arguments are specified. For example, it will expand -xzf into -x -z -f. It adds a -- argument after all the flags which separates flags from file names; this lets you do run cat -- -my-file to display the contents of -my-file without barfing on the leading dash.
Try this boilerplate code on for size:
#!/bin/bash
eval set -- "$(getopt -o a:bch -l alpha:,bravo,charlie,help -n "$0" -- "$#")"
while [[ $1 != -- ]]; do
case "$1" in
-a|--alpha)
echo "--alpha $2"
shift 2
;;
-b|--bravo)
echo "--bravo"
shift
;;
-c|--charlie)
echo "--charlie"
shift
;;
-h|--help)
echo "Usage: $0 [-a ARG] [-b] [-c]" >&2
exit 1
;;
esac
done
shift
Notice that each option has a short a long equivalent, e.g. -a and --alpha. The -a flag takes an argument so it's specified as a: and alpha: in the getopt call, and has a shift 2 at the end of its case.
Another way to iterate over the arguments which is closer to what you were working toward would be something like:
for ((i=1; i<=$#; i++))
do
echo "${#:i:1}"
done
but the for arg syntax that John Kugelman showed is by far preferable. There are, however, times when array slicing is useful. Also, in this version, as in John's, the argument array is left intact. Using shift discards its elements.
You should note that what you were trying to do with square brackets is not a regular expression at all.
I suggest doing something else instead:
while [ -n "$1" ] ; do
# Do something with $1
shift
# Now whatever was in $2 is now in $1
done
The shift keyword moves the content of $2 into $1, $3 into $2, etc. pp.
Let's say the arguments where:
a b c d
After a shift, the arguments are now:
b c d
With the while loop, you can thus parse an arbitrary number of arguments and can even do things like:
while [ -n "$1" ] ; do
if [ "$1" = "-f" ] ; then
shift
if [ -n "$1" ] ; then
myfile="$1"
else
echo "-f needs an additional argument"
end
fi
shift
done
Imagine the arguments as being an array and $n being indexes into that array. shift removes the first element, so the index 1 now references the element that was at index 2 prior to shift. I hope you understand what I want to say.

Resources