How to get everything after two specific characters in bash - bash

I want to put the numbers into different variables
So I have three lines for example with $1 being -a in the first line
number -a 3 1245 1234
second line, $1= -b
number -b 2 2345 64353
third line, $1= -a
number -a 5 -b 3 54525 545252
Is it possible to put the numbers in one variable if a and b are separated like line one and two and in two variables if they are combined
So for line one
number=3
line 2
number=2
line 3
number1=5
number2=3
and then to use in a echo statement

Just do normal shell parsing for command line parameters. Not practical to create conditions for parsing for 3rd pattern "number=". Best to keep it to one for each of the options involved, that way you keep the distinct relationship with the original option flags.
#!/bin/sh
echo "number -a 3 1245 1234
number -b 2 2345 64353
number -a 5 -b 3 54525 545252" |
while read line
do
set -- ${line}
shift # eliminate command from line
while [ $# -gt 1 ]
do
case $1 in
-a ) echo "number1=$2" ; shift ; shift ;;
-b ) echo "number2=$2" ; shift ; shift ;;
* ) break ;;
esac
done
echo ""
done

Related

In bash how to use the last argument- and adding all other arguments to array

I have a script where the user can add as many arguments as he would like (numbers).
The script will sum all the numbers beside the last number - The last number (argument) is the number that I need to divide by
For example:
./test.sh 2 2 6 5
This will sum the first 3 numbers (2+2+6) and divide the answer by 5 (the last argument)
How can I use the last argument? Echo ????
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Please note that the number of arguments can be changed
How can I use the last argument? Echo ????
Granting $# > 0, you can use "${!#}".
How can I move loop the first arguments besides the last one – I would
like that all 3 arguments will be added to an array and I can loop it
Again granting $# > 0, you can refer to "${#:1:$# - 1}".
Read the Arrays section in the bash manual to know how to properly expand arrays.
I also recommend learning how quoting works and knowing the dangers of unwanted word splitting and globbing.
Shortly (with bashisms)
As this question is tagged integer-arithmetic and bash:
Here is a small and efficient script:
#!/bin/bash
addVals=${*: 1 : $# - 1}
declare -i intResult=" ( ${addVals// /+} ) / ${#: -1} "
echo $intResult
But there's no loop...
Long answer
How can I use the last argument? Echo ????
You could make your tries in command line:
set -- 2 2 6 5
Then
echo $#
2 2 6 5
echo ${*: 3}
6 5
echo ${*: -1}
5
echo ${*: 1 : -1}
bash: -1: substring expression < 0
echo $#
4
echo ${*: 1 : $# -1}
2 2 6
Ok, then
someVar=${*: 1 : $# -1}
echo ${someVar// /:SpaceReplacment:}
2:SpaceReplacment:2:SpaceReplacment:6
so
declare -i result
result=" ( ${someVar// /+} ) / ${*: -1} "
echo $result
2
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Still forward, under command line...
someArray=("${#: 1: $# -1 }")
Use declare -p to show $someArray's content:
declare -p someArray
declare -a someArray=([0]="2" [1]="2" [2]="6")
Then
declare -i mySum=0
for i in "${someArray[#]}";do
mySum+=i
done
echo $mySum
10
echo $(( mySum / ${*: -1} ))
2
Please note that the number of arguments can be changed
Please note:
Using double quotes allow processing of strings containing spaces:
set -- foo bar 'foo bar baz'
echo ${2}
bar
echo ${*: $# }
foo bar baz
Difference betweeen use of "$#" (array to array) and "$*" (array to string)
set -- foo bar 'foo bar' 'foo bar baz'
If I take 3 first elements:
someArray=("${#: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo" [1]="bar" [2]="foo bar")
But
someArray=("${*: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo bar foo bar")
There are about a thousand ways of doing this. As you would like to make use of integer arithmetic, you can do the following in bash
A short semi-cryptic version would be:
IFS=+
echo $(( ( ${*} - ${#:-1} ) / ${#:-1} ))
Here we make use of the difference between "${*}" and "${#}" to perform the sum by setting IFS=+ (See What is the difference between "$#" and "$*" in Bash?)
A long classic approach would be:
for i in "$#"; do ((s+=i)); done
echo $(( (s-${#:-1})/${#:-1} ))
It's easier to sum all terms and subtract the last term afterwards

How to process more than 2 arguments in Bash? [duplicate]

This question already has answers here:
An example of how to use getopts in bash
(8 answers)
Closed 3 years ago.
So, I want to pass 2 arguments and want those arguments to be processed in combination and separately too. I have coded the switch case to process them separately but I am not sure how to do process them together.
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
Here,
-a: at least number of letters specified.
-b: at most number of letters specified.
What I am looking for?
when I get -a and -b together, first the script should list all the words which have at least given number of words, save, then out of those, it should process with the -b, that is the most number of letters in the result we got from the -a, then print the count.
This is a double filter, it is not like you posted both of them individually.
while [[ $# -gt 0 ]]; do
case $1 in
-a)
argument=$
egrep "^[[:alnum:]]{$argument,}$" $dictionyname | wc -l
shift ;;
-b)
arg=$2
egrep "^[[:alnum:]]{0,$argument}$" $dictionyname | wc -l
shift ;;
esac
shift
done
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
7000
$ sh match_the_pattern.sh -a 6 words.txt
115690
$ sh match_the_pattern.sh -b 5 words.txt
12083
Note, I do know how to process -a and -b, I do not know how to combine the results of -a and -b when passed together in the argument...
If right now, I pass the command, I am getting this output :
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
115690
12083
So it is processing a and b but giving the results one after another.
Set variables based on the parameters, then use them after the loop.
min=
max=
while [[ $# -gt 0 ]]; do
case $1 in
-a)
min=$2
shift ;;
-b)
max=$2
shift ;;
-*)
echo "Unknown option $1"
exit 1 ;;
*)
break ;;
esac
shift
done
if [[ -z $min && -z $max ]]
then
echo "At least one of -a and -b must be used"
exit 1
fi
egrep "^[[:alnum:]]{${min:-1},$max}$" "$#" | wc -l
${min:-1} means to use the value of $min, but if it's empty use 1 instead. So if they don't give -a, it will be {1,$max}.

Nested quotes in Bash

Please let me explain my scenario with some sample script which is much simpler than my actual application but shows the same behavior:
For the demo I use two bash shell scripts. The first one is called argtest.sh simply outputs the command line parameters:
#/bin/bash
echo "Argument 1: $1"
echo "Argument 2: $2"
Test:
root#test:~# ./argtest.sh 1 2
Argument 1: 1
Argument 2: 2
and
root#test:~# ./argtest.sh "1 2" 3
Argument 1: 1 2
Argument 2: 3
This works as expected
I've created another command like this:
./argtest.sh $(for i in ` seq 1 2 ` ; do echo Number $i; done)
This is the result
Argument 1: Number
Argument 2: 1
I've tried many different combinations of quotation marks (") and escaped quotation marks (\") around Number, but no combination produced the desired output of:
Argument 1: Number 1
Argument 2: Number 2
How can I get to that output?
You can't. The command substitution is subject to word-splitting. Its output is two lines
Number 1
Number 2
but newlines are treated as arbitrary whitespace during word splitting. As a result, the command substitution is split into 4 words (Number, 1, Number, and 2), each of which is a separate argument to argtest.sh.
Additional quotes would treated literally during word-splitting; a string like "a b" c is split into 3 words ("a, b", and c), not 2 (a b, c).
To do what you want, you need to use a while loop to read one line of output at a time from your loop. For example:
for i in $(seq 1 2); do
echo "Number $i"
done | while read -r line; do
./argtest.sh "$line"
done
because arguments are subject to word splitting, you may change $IFS and put the the new separator into your echo
IFS=$'_\n'
./argtest.sh $(for i in {1..2} ; do echo "Number ${i}_"; done )
don't forget to restore the old $IFS
oIFS=$IFS
...
IFS=$oIFS
btw {1..2} is equivalent to seq 1 2 but you don't need an external command
As chepner mentioned, command substitution is subjected to word splitting.
Below are few examples to better understand this
Here is the behaviour when I replace echo with printf and escape the double quotes:-
for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n"
"Number 1" "Number 2"
./arg.sh $(for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n")
Argument 1: "Number
Argument 2: 1"
Now if I try to enclose the whole command substitution in double quotes, it will consider them as one single argument:-
./arg.sh "$(for i in ` seq 1 2 ` ; do printf "\"Number %d\" " "$i"; done; printf "\n")"
Argument 1: "Number 1" "Number 2"
Argument 2:
You can use xargs
for i in ` seq 1 2 ` ; do echo Number $i; done | xargs -d $'\n' ./argtest.sh

Process files in pairs

I have a list of files:
file_name_FOO31101.txt
file_name_FOO31102.txt
file_name_FOO31103.txt
file_name_FOO31104.txt
And I want to use pairs of files for input into a downstream program such as:
program_call file_name_01.txt file_name_02.txt
program_call file_name_03.txt file_name_04.txt
...
I do not want:
program_call file_name_02.txt file_name_03.txt
I need to do this in a loop as follows:
#!/bin/bash
FILES=path/to/files
for file in $FILES/*.txt;
do
stem=$( basename "${file}" ) # stem : file_name_FOO31104_info.txt
output_base=$( echo $stem | cut -d'_' -f 1,2,3 ) # output_base : FOO31104_info.txt
id=$( echo $stem | cut -d'_' -f 3 ) # get the first field : FOO31104
number=$( echo -n $id | tail -c 2 ) # get the last two digits : 04
echo $id $((id+1))
done
But this does not produce what I want.
In each loop I want to call a program once, with two files as input (last 2 digits of first file always odd 01, last 2 digits of second file always even 02)
I actually wouldn't use a for loop at all. A while loop that shifts files off is a perfectly reasonable way to do this.
# here, we're overriding the argument list with the list of files
# ...you can do this in a function if you want to keep the global argument list intact
set -- "$FILES"/*.txt ## without these quotes paths with spaces break
# handle the case where no files were found matching our glob
[[ -e $1 || -L $1 ]] || { echo "No .txt found in $FILES" >&2; exit 1; }
# here, we're doing our own loop over those arguments
while (( "$#" > 1 )); do ## continue in the loop only w/ 2-or-more remaining
echo "Processing files $1 and $2" ## ...substitute your own logic here...
shift 2 || break ## break even if test doesn't handle this case
done
# ...and add your own handling for the case where there's an odd number of files.
(( "$#" )) && echo "Left over file $1 still exists"
Note that the $#s are quoted inside (( )) here for StackOverflow's syntax highlighting, not because they otherwise need to be. :)
By the way -- consider using bash's native string manipulation.
stem=${file##*/}
IFS=_ read -r p1 p2 id p_rest <<<"$stem"
number=${id:$(( ${#id} - 2 ))}
output_base="${p1}${p2}${id}"
echo "$id $((10#number + 1))" # 10# ensures interpretation as decimal, not octal

how do you make a reverse number pyramid in bash

I'm trying to make a reverse pyramid that outputs
$ ./reverse_pyramid **3**
1 2 3 2 1
1 2 1
1
This is what I have but it outputs nothing and has no syntax errors:
#!/bin/bash
# get input
read -p "Enter number:" num
#outside of pyramid
for((i=1;i>=num;i--))
do
#Loop to print numbers
for((s=i;s>=num;s--))
do
echo -ne "#"
done
#left half
for((j=1;j<=i;j++))
do
echo -ne "$j"
done
#right
for((l=(i-1);l>=1;l--))
do
echo -ne "$l"
done
#add a line
echo
done
It's due to this line:
for((i=1;i>=num;i--))
you're setting i to 1 which is smaller than your input num of 3 so your loop won't execute.
As a side note, I recommend you use -x option when debugging bash programs:
bash -x myprogram.sh
This will show you all the execution steps and pinpoints where it might be going wrong

Resources