Extract part between two strings in a variable in Unix - bash

I have a string,
tester_one="update set_tables set abc=7 where bcd=9"
Here I wish to extract only the part between "set" and "where",
abc=7
I tried a couple of Unix commands, but it picked up any occurrences of set or where encountered, before the part where I want it to pick up.
I have an idea on how to do it Java but I am lost in Unix as I am new to this.

Using sed
$ echo "$tester_one" | sed -E 's/.*set (.*) where.*/\1/'
abc=7
To capture it in a variable:
$ new=$(echo "$tester_one" | sed -E 's/.*set (.*) where.*/\1/')
$ echo $new
abc=7
Using awk
$ echo "$tester_one" | awk '{sub(/.*set /,""); sub(/ where.*/,""); print;}'
abc=7
Using grep -P
If your grep supports the -P (perl-like) option:
$ echo "$tester_one" | grep -oP '(?<=set ).*(?= where)'
abc=7

You can get it out with sed. Something like:
echo "$tester_one" | sed 's/.* set \(.*\) where .*/\1/'

Using Bash Pattern Matching:
#!/bin/bash
tester_one="update set_tables set abc=7 where bcd=9"
pat=".* set (.*) where"
[[ $tester_one =~ $pat ]]
echo "${BASH_REMATCH[1]}"

You can also use set and where as field separators and print the field that lies in between them:
$ awk -F"set | where" '{print $2}' <<< "update set_tables set abc=7 where bcd=9"
abc=7

As with your other question, this can be achieved in pure bash, without the use of external tools like sed/awk/grep.
#!/usr/bin/env bash
tester_one="update set_tables set abc=7 where bcd=9"
output="${tester_one#* set }"
output="${output% where }"
echo "$output"
Note the spaces around "set" and "where" in the parameter expansion lines. As you might expect, you'll need to be careful with this if the $tester_one variable contains the distinct "set" or "where" in places that you don't expect them.
That said, I like Jahid's answer better. :-)

Related

How to split a string on the second match

I have a string:
foo="re-9619-add-selling-office";
I'd like to break up the string on the second - (dash) into variable1 and variable2. I want to end up with variable1=re-9619 and variable2=add-selling-office
I tried it using grep and awk, but now I not sure that's the way to go.
Here is a single sed + read way:
foo="re-9619-add-selling-office"
read var1 var2 < <(sed -E 's/^([^-]*-[^-]*)-/\1 /' <<< "$foo")
# check variables
declare -p var1 var2
declare -- var1="re-9619"
declare -- var2="add-selling-office"
Could you please try following once. Where first variable will have value like re-9619 and second shell variable will have value like add-selling-office
first=$(echo "$foo" | sed 's/\([^-]*-[^-]*\)-.*/\1/')
second=$(echo "$foo" | sed 's/\([^-]*\)-\([^-]*\)-\(.*\)/\3/')
Explanation:
echo "$foo" | sed 's/\([^-]*-[^-]*\)-.*/\1/': Printing value of foo variable and passing its output to sed command. In sed I am using substitute capability to perform substitution, \([^-]*-[^-]*\)-.*(which has everything from starting of value to till 2nd occurrence of - in back reference in it). Then substituting whole value with 1st captured back reference value which will become only re-9619.
echo "$foo" | sed 's/\([^-]*\)-\([^-]*\)-\(.*\)/\3/': Logic is same as above mentioned command. Using sed's capability of substitution with using back reference capability of it. Here we are printing everything after 2nd occurrence of -.
NOTE: second=$(echo "$foo" | sed -E "s/$first-(.*)/\1/") could also help as per #User123's comments.
That can be done using parameter expansions, you don't need an external utility.
$ foo="re-9619-add-selling-office"
$ variable2=${foo#*-*-}
$ variable1=${foo%-"$variable2"}
$
$ echo $variable1
re-9619
$ echo $variable2
add-selling-office
You can use cut:
variable1=$(echo $foo | cut -d '-' -f 1-2)
variable2=$(echo $foo | cut -d '-' -f 3-)
This is the result:
>> echo $variable1
re-9619
>> echo $variable2
add-selling-office

How to filter an ordered list stored into a string

Is it possible in bash to filter out a part of a string with another given string ?
I have a fixed list of motifs defined in a string. The order IS important and I want to keep only the parts that are passed as a parameter ?
myDefaultList="s,t,a,c,k" #order is important
toRetains="k,t,c,u" #provided by the user, order is not enforced
retained=filter $myDefaultList $toRetains # code to filter
echo $retained # will print t,c,k"
I can write an ugly method that will use IFS, arrays and loops, but I wonder if there's a 'clever' way to do that, using built-in commands ?
here is another approach
tolines() { echo $1 | tr ',' '\n'; }
grep -f <(tolines "$toRetains") <(tolines "$myDefaultList") | paste -sd,
will print
t,c,k
assign to a variable as usual.
Since you mention in your comments that you are open to sed/awk , check also this with GNU awk:
$ echo "$a"
s,t,a,c,k
$ echo "$b"
k,t,c,u
$ awk -v RS=",|\n" 'NR==FNR{a[$1];next}$1 in a{printf("%s%s",$1,RT)}' <(echo "$b") <(echo "$a")
t,c,k
#!/bin/bash
myDefaultList="s,t,a,c,k"
toRetains="s,t,c,u"
IFS=","
for i in $myDefaultList
do
echo $toRetains | grep $i > /dev/null
if [ "$?" -eq "0" ]
then
retained=$retained" "$i
fi
done
echo $retained | sed -e 's/ /,/g' -e 's/,//1'
I have checked it running for me. Kindly check.

How to remove special characters from strings but keep underscores in shell script

I have a string that is something like "info_A!__B????????C_*". I wan to remove the special characters from it but keep underscores and letters. I tried with [:word:] (ASCII letters and _) character set, but it says "invalid character set". any idea how to handle this ? Thanks.
text="info_!_????????_*"
if [ -z `echo $text | tr -dc "[:word:]"` ]
......
Using bash parameter expansion:
$ var='info_A!__B????????C_*'
$ echo "${var//[^[:alnum:]_]/}"
info_A__BC_
A sed one-liner would be
sed 's/[^[:alnum:]_]//g' <<< 'info_!????????*'
gives you
info_
An awk one-liner would be
awk '{gsub(/[^[:alnum:]_]/,"",$0)} 1' <<< 'info_!??A_??????*pi9ngo^%$_mingo745'
gives you
info_A_pi9ngo_mingo745
If you don't wish to have numbers in the output then change :alnum: to :alpha:.
My tr doesn't understand [:word:]. I had to do like this:
$ x=$(echo 'info_A!__B????????C_*' | tr -cd '[:alnum:]_')
$ echo $x
info_A__BC_
Not sure if its robust way but it worked for your sample text.
sed one-liner:
echo "SamPlE_#tExT%, really ?" | sed -e 's/[^a-z^A-Z|^_]//g'
SamPlE_tExTreally

bash script command output execution doesn't assign full output when using backticks

I used many times [``] to capture output of command to a variable. but with following code i am not getting right output.
#!/bin/bash
export XLINE='($ZWP_SCRIP_NAME),$ZWP_LT_RSI_TRIGGER)R),$ZWP_RTIMER'
echo 'Original XLINE'
echo $XLINE
echo '------------------'
echo 'Extract all word with $ZWP'
#works fine
echo $XLINE | sed -e 's/\$/\n/g' | sed -e 's/.*\(ZWP[_A-Z]*\).*/\1/g' | grep ZWP
echo '------------------'
echo 'Assign all word with $ZWP to XVAR'
#XVAR doesn't get all the values
export XVAR=`echo $XLINE | sed -e 's/\$/\n/g' | sed -e 's/.*\(ZWP[_A-Z]*\).*/\1/g' | grep ZWP` #fails
echo "$XVAR"
and i get:
Original XLINE
($ZWP_SCRIP_NAME),$ZWP_LT_RSI_TRIGGER)R),$ZWP_RTIMER
------------------
Extract all word with $ZWP
ZWP_SCRIP_NAME
ZWP_LT_RSI_TRIGGER
ZWP_RTIMER
------------------
Assign all word with $ZWP to XVAR
ZWP_RTIMER
why XVAR doesn't get all the values?
however if i use $() to capture the out instead of ``, it works fine. but why `` is not working?
Having GNU grep you can use this command:
XVAR=$(grep -oP '\$\KZWP[A-Z_]+' <<< "$XLINE")
If you pass -P grep is using Perl compatible regular expressions. The key here is the \K escape sequence. Basically the regex matches $ZWP followed by one or more uppercase characters or underscores. The \K after the $ removes the $ itself from the match, while its presence is still required to match the whole pattern. Call it poor man's lookbehind if you want, I like it! :)
Btw, grep -o outputs every match on a single line instead of just printing the lines which match the pattern.
If you don't have GNU grep or you care about portability you can use awk, like this:
XVAR=$(awk -F'$' '{sub(/[^A-Z_].*/, "", $2); print $2}' RS=',' <<< "$XLINE")
First, the smallest change that makes your code "work":
echo "$XLINE" | tr '$' '\n' | sed -e 's/.*\(ZWP[_A-Z]*\).*/\1/g' | grep ZWP_
The use of tr replaces a sed expression that didn't actually do what you thought it did -- try looking at its output to see.
One sane alternative would be to rely on GNU grep's -o option. If you can't do that...
zwpvars=( ) # create a shell array
zwp_assignment_re='[$](ZWP_[[:alnum:]_]+)(.*)' # ...and a regex
content="$XLINE"
while [[ $content =~ $zwp_assignment_re ]]; do
zwpvars+=( "${BASH_REMATCH[1]}" ) # found a reference
content=${BASH_REMATCH[2]} # stuff the remaining content aside
done
printf 'Found variable: %s\n' "${zwpvars[#]}"

Split String in Unix Shell Script

I have a String like this
//ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf
and want to get last part of
00000000957481f9-08d035805a5c94bf
Let's say you have
text="//ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf"
If you know the position, i.e. in this case the 9th, you can go with
echo "$text" | cut -d'/' -f9
However, if this is dynamic and your want to split at "/", it's safer to go with:
echo "${text##*/}"
This removes everything from the beginning to the last occurrence of "/" and should be the shortest form to do it.
For more information on this see: Bash Reference manual
For more information on cut see: cut man page
The tool basename does exactly that:
$ basename //ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf
00000000957481f9-08d035805a5c94bf
I would use bash string function:
$ string="//ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf"
$ echo "${string##*/}"
00000000957481f9-08d035805a5c94bf
But following are some other options:
$ awk -F'/' '$0=$NF' <<< "$string"
00000000957481f9-08d035805a5c94bf
$ sed 's#.*/##g' <<< "$string"
00000000957481f9-08d035805a5c94bf
Note: <<< is herestring notation. They do not create a subshell, however, they are NOT portable to POSIX sh (as implemented by shells such as ash or dash).
In case you want more than just the last part of the path,
you could do something like this:
echo $PWD | rev | cut -d'/' -f1-2 | rev
You can use this BASH regex:
s='//ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf'
[[ "$s" =~ [^/]+$ ]] && echo "${BASH_REMATCH[0]}"
00000000957481f9-08d035805a5c94bf
This can be done easily in awk:
string="//ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf"
echo "${string}" | awk -v FS="/" '{ print $NF }'
Use "/" as field separator and print the last field.
You can try this...
echo //ABC/REC/TLC/SC-prod/1f9/20/00000000957481f9-08d035805a5c94bf |awk -F "/" '{print $NF}'

Resources