RegEx in bash-script (for-loop) - linux

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.

Related

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))

How to create a string=$* without arguments $1 and $2

I have a script that takes in several arguments.
I need everything but $1 and $2 in a string.
I have tried this:
message="$*"
words= $(grep -v "$2"|"$3" $message)
but it doesn't work, it gives me the error:
./backup: line 26: First: command not found
Use shift 2 to shift the arguments along (it drops the first n arguments).
If you need "$1" and "$2" for later, save them in variables first.
Note that in shell, assignments to variables cannot have whitespace either side of the =.
First=$1
Second=$2
shift 2
Message=$#
Maybe something like this?
[root#tsekmanrhel771 ~]# cat ./skip1st2.sh
#!/bin/bash
COUNT=0
for ARG in "$#"
do
COUNT=$[COUNT + 1]
if [ ${COUNT} -gt 2 ]; then
RESULT="${RESULT} ${ARG}"
fi
done
echo ${RESULT}
[root#tsekmanrhel771 ~]# ./skip1st2.sh first second third 4 5 6 7
third 4 5 6 7
You can use a subarray:
$ set -- arg1 arg2 arg3 arg4
$ str=${*:3}
$ echo "$str"
arg3 arg4
More often than not, it's good practice to preserve the arguments as separate elements, though, which you can do by using $# and assigning to a new array:
$ arr=("${#:3}")
$ declare -p arr
declare -a arr=([0]="arg3" [1]="arg4")
Notice that in str=${*:3}, quoting isn't necessary, but in arr=("${#:3}"), it is (or the arguments would be split on whitespace).
As for your error message: your command
words= $(grep -v "$2"|"$3" $message)
does the following:
It sets a variable words to the empty string for the environment of the command (because there is a blank after =).
It tries to set up a pipeline consisting of two commands, grep -v "$2" and "$3" $message. The first of these commands would just hang and wait for input; the second one tries to run the contents of $3 as a command; presumably, based on your error message, $3 contains First.
If the pipeline would actually run, its output would be run as a command (again because of the blank to the right of =).

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

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

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

concatenate inputs in bash script [duplicate]

This question already has answers here:
Concatenate all arguments and wrap them with double quotes
(6 answers)
Closed 5 years ago.
I would like to concatenate all the arguments passed to my bash script except the flag.
So for example, If the script takes inputs as follows:
./myBashScript.sh -flag1 exampleString1 exampleString2
I want the result to be "exampleString1_exampleString2"
I can do this for a predefined number of inputs (i.e. 2), but how can i do it for an arbitrary number of inputs?
function concatenate_args
{
string=""
for a in "$#" # Loop over arguments
do
if [[ "${a:0:1}" != "-" ]] # Ignore flags (first character is -)
then
if [[ "$string" != "" ]]
then
string+="_" # Delimeter
fi
string+="$a"
fi
done
echo "$string"
}
# Usage:
args="$(concatenate_args "$#")"
This is an ugly but simple solution:
echo $* | sed -e "s/ /_/g;s/[^_]*_//"
You can also use formatted strings to concatenate args.
# assuming flag is first arg and optional
flag=$1
[[ $1 = ${1#-} ]] && unset $flag || shift
concat=$(printf '%s_' ${#})
echo ${concat%_} # to remove the trailing _
nJoy!
Here's a piece of code that I'm actually proud of (it is very shell-style I think)
#!/bin/sh
firsttime=yes
for i in "$#"
do
test "$firsttime" && set -- && unset firsttime
test "${i%%-*}" && set -- "$#" "$i"
done
IFS=_ ; echo "$*"
I've interpreted your question so as to remove all arguments beginning with -
If you only want to remove the beginning sequence of arguments beginnnig with -:
#!/bin/sh
while ! test "${1%%-*}"
do
shift
done
IFS=_ ; echo "$*"
If you simply want to remove the first argument:
#!/bin/sh
shift
IFS=_ ; printf %s\\n "$*"
flag="$1"
shift
oldIFS="$IFS"
IFS="_"
the_rest="$*"
IFS="$oldIFS"
In this context, "$*" is exactly what you're looking for, it seems. It is seldom the correct choice, but here's a case where it really is the correct choice.
Alternatively, simply loop and concatenate:
flag="$1"
shift
the_rest=""
pad=""
for arg in "$#"
do
the_rest="${the_rest}${pad}${arg}"
pad="_"
done
The $pad variable ensures that you don't end up with a stray underscore at the start of $the_rest.
#!/bin/bash
paramCat () {
for s in "$#"
do
case $s in
-*)
;;
*)
echo -n _${s}
;;
esac
done
}
catted="$(paramCat "$#")"
echo ${catted/_/}

Resources