Splitting a text in Unix - bash

I am writing a simple script that splits a variable that holds some text by using below code:
#!/bin/sh
SAMPLE_TEXT=hello.world.testing
echo $SAMPLE_TEXT
OUT_VALUE=$SAMPLE_TEXT | cut -d'.' -f1
echo output is $OUT_VALUE
I am expecting output as output is hello but when I run this program then I am getting output as output is. Please let me know where I am doing mistake?

To evaluate a command and store it into a variable, use var=$(command).
All together, your code works like this:
SAMPLE_TEXT="hello.world.testing"
echo "$SAMPLE_TEXT"
OUT_VALUE=$(echo "$SAMPLE_TEXT" | cut -d'.' -f1)
# OUT_VALUE=$(cut -d'.' -f1 <<< "$SAMPLE_TEXT") <--- alternatively
echo "output is $OUT_VALUE"
Also, note I am adding quotes all around. It is a good practice that will help you in general.
Other approaches:
$ sed -r 's/([^\.]*).*/\1/g' <<< "$SAMPLE_TEXT"
hello
$ awk -F. '{print $1}' <<< "$SAMPLE_TEXT"
hello
$ echo "${SAMPLE_TEXT%%.*}"
hello

The answer by fedorqui is the correct answer. Just adding another approach...
$ SAMPLE_TEXT=hello.world.testing
$ IFS=. read OUT_VALUE _ <<< "$SAMPLE_TEXT"
$ echo output is $OUT_VALUE
output is hello

Just to expand on #anishane's comment to his own answer:
$ SAMPLE_TEXT="hello world.this is.a test string"
$ IFS=. read -ra words <<< "$SAMPLE_TEXT"
$ printf "%s\n" "${words[#]}"
hello world
this is
a test string
$ for idx in "${!words[#]}"; do printf "%d\t%s\n" $idx "${words[idx]}"; done
0 hello world
1 this is
2 a test string

Related

Bash scripting with IFS

I need in Bash with IFS=‘,’
./test.sh Mike,Texas Nik,Toronto Lucas,Iowa
Hello Mike
You are from Texas
Hello Nik
You are from Toronto
Hello Lucas
You are from Iowa
#!/bin/bash
IFS=',' read -r -a array <<< "$string"
for index in "${!array[#]}"
do
echo Hello "$index ${array[index]}"
echo You are from "$index ${array[index]}"
done
You don't need to modify IFS (the Internal Field Separator), you need parameter expansion with substring removal, e.g.
#!/bin/bash
for i in "$#"; do
printf "Hello %s\nYou are from %s\n" "${i%,*}" "${i#*,}"
done
Example Use/Output
$ ./test.sh Mike,Texas Nik,Toronto Lucas,Iowa
Hello Mike
You are from Texas
Hello Nik
You are from Toronto
Hello Lucas
You are from Iowa
You can loop over arguments using just for argument - this will populate the variable named argument with the space separated arguments one at a time. Then you can read the comma-separated values. For example:
set -- Mike,Texas Nik,Toronto Lucas,Iowa # For testing purposes
for argument
do
IFS=, read name state <<< "$parameter"
echo "Hello ${name}"
echo "You are from ${state}"
done
Based on what I see, here is my suggestion:
for i in $* ; do
name=$(echo $i | awk -F, '{print $1}' )
wherefrom=$(echo $i | awk -F, '{print $2'})
echo Hello $name, you are from $wherefrom
done

Unix file pattern issue: append changing value of variable pattern to copies of matching line

I have a file with contents:
abc|r=1,f=2,c=2
abc|r=1,f=2,c=2;r=3,f=4,c=8
I want a result like below:
abc|r=1,f=2,c=2|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|3
The third column value is r value. A new line would be inserted for each occurrence.
I have tried with:
for i in `cat $xxxx.txt`
do
#echo $i
live=$(echo $i | awk -F " " '{print $1}')
home=$(echo $i | awk -F " " '{print $2}')
echo $live
done
but is not working properly. I am a beginner to sed/awk and not sure how can I use them. Can someone please help on this?
awk to the rescue!
$ awk -F'[,;|]' '{c=0;
for(i=2;i<=NF;i++)
if(match($i,/^r=/)) a[c++]=substr($i,RSTART+2);
delim=substr($0,length($0))=="|"?"":"|";
for(i=0;i<c;i++) print $0 delim a[i]}' file
abc|r=1,f=2,c=2|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|3
Use an inner routine (made up of GNU grep, sed, and tr) to compile a second more elaborate sed command, the output of which needs further cleanup with more sed. Call the input file "foo".
sed -n $(grep -no 'r=[0-9]*' foo | \
sed 's/^[0-9]*/&s#.*#\&/;s/:r=/|/;s/.*/&#p;/' | \
tr -d '\n') foo | \
sed 's/|[0-9|]*|/|/'
Output:
abc|r=1,f=2,c=2|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|3
Looking at the inner sed code:
grep -no 'r=[0-9]*' foo | \
sed 's/^[0-9]*/&s#.*#\&/;s/:r=/|/;s/.*/&#p;/' | \
tr -d '\n'
It's purpose is to parse foo on-the-fly (when foo changes, so will the output), and in this instance come up with:
1s#.*#&|1#p;2s#.*#&|1#p;2s#.*#&|3#p;
Which is almost perfect, but it leaves in old data on the last line:
sed -n '1s#.*#&|1#p;2s#.*#&|1#p;2s#.*#&|3#p;' foo
abc|r=1,f=2,c=2|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|1
abc|r=1,f=2,c=2;r=3,f=4,c=8|1|3
...which old data |1 is what the final sed 's/|[0-9|]*|/|/' removes.
Here is a pure bash solution. I wouldn't recommend actually using this, but it might help you understand better how to work with files in bash.
# Iterate over each line, splitting into three fields
# using | as the delimiter. (f3 is only there to make
# sure a trailing | is not included in the value of f2)
while IFS="|" read -r f1 f2 f3; do
# Create an array of variable groups from $f2, using ;
# as the delimiter
IFS=";" read -a groups <<< "$f2"
for group in "${groups[#]}"; do
# Get each variable from the group separately
# by splitting on ,
IFS=, read -a vars <<< "$group"
for var in "${vars[#]}"; do
# Split each assignment on =, create
# the variable for real, and quit once we
# have found r
IFS== read name value <<< "$var"
declare "$name=$value"
[[ $name == r ]] && break
done
# Output the desired line for the current value of r
printf '%s|%s|%s\n' "$f1" "$f2" "$r"
done
done < $xxxx.txt
Changes for ksh:
read -A instead of read -a.
typeset instead of declare.
If <<< is a problem, you can use a here document instead. For example:
IFS=";" read -A groups <<EOF
$f2
EOF

Weird bash results using cut

I am trying to run this command:
./smstocurl SLASH2.911325850268888.911325850268896
smstocurl script:
#SLASH2.911325850268888.911325850268896
model=$(echo \&model=$1 | cut -d'.' -f 1)
echo $model
imea1=$(echo \&simImea1=$1 | cut -d'.' -f 2)
echo $imea1
imea2=$(echo \&simImea2=$1 | cut -d'.' -f 3)
echo $imea2
echo $model$imea1$imea2
Result Received
&model=SLASH2911325850268888911325850268896
Result Expected
&model=SLASH2&simImea1=911325850268888&simImea2=911325850268896
What am I missing here ?
You are cutting based on the dot .. In the first case your desired string contains the first string, the one containing &model, so then it is printed.
However, in the other cases you get the 2nd and 3rd blocks (-f2, -f3), so that the imea text gets cutted off.
Instead, I would use something like this:
while IFS="." read -r model imea1 imea2
do
printf "&model=%s&simImea1=%s&simImea2=%s\n" $model $imea1 $imea2
done <<< "$1"
Note the usage of printf and variables to have more control about what we are writing. Using a lot of escapes like in your echos can be risky.
Test
while IFS="." read -r model imea1 imea2; do printf "&model=%s&simImea1=%s&simImea2=%s\n" $model $imea1 $imea2
done <<< "SLASH2.911325850268888.911325850268896"
Returns:
&model=SLASH2&simImea1=911325850268888&simImea2=911325850268896
Alternatively, this sed makes it:
sed -r 's/^([^.]*)\.([^.]*)\.([^.]*)$/\&model=\1\&simImea1=\2\&simImea2=\3/' <<< "$1"
by catching each block of words separated by dots and printing back.
You can also use this way
Run:
./program SLASH2.911325850268888.911325850268896
Script:
#!/bin/bash
String=`echo $1 | sed "s/\./\&simImea1=/"`
String=`echo $String | sed "s/\./\&simImea2=/"`
echo "&model=$String
Output:
&model=SLASH2&simImea1=911325850268888&simImea2=911325850268896
awk way
awk -F. '{print "&model="$1"&simImea1="$2"&simImea2="$3}' <<< "SLASH2.911325850268888.911325850268896"
or
awk -F. '$0="&model="$1"&simImea1="$2"&simImea2="$3' <<< "SLASH2.911325850268888.911325850268896"
output
&model=SLASH2&simImea1=911325850268888&simImea2=911325850268896

Is there a better way to retrieve the elements of a delimited pair in bash?

I have entries of the form: cat:rat and I would like to assign them to separate variables in bash. I am currently able to do this via:
A=$(echo $PAIR | tr ':' '\n' | head -n1)
B=$(echo $PAIR | tr ':' '\n' | tail -n1)
after which $A and $B are, respectively, cat and rat. echo, the two pipes and all feels a bit like overkill am I missing a much simpler way of doing this?
Using the read command
entry=cat:rat
IFS=: read A B <<< "$entry"
echo $A # => cat
echo $B # => rat
Yes using bash parameter substitution
PAIR='cat:rat'
A=${PAIR/:*/}
B=${PAIR/*:/}
echo $A
cat
echo $B
rat
Alternately, if you are willing to use an array in place of individual variables:
IFS=: read -r -a ARR <<<"${PAIR}"
echo ${ARR[0]}
cat
echo ${ARR[1]}
rat
EDIT: Refer glenn jackman's answer for the most elegant read-based solution
animal="cat:rat"
A=echo ${animal} | cut -d ":" -f1
B=echo ${animal} | cut -d ":" -f2
might not be the best solution. Just giving you a possible solution

Redirect output to a bash array

I have a file containing the string
ipAddress=10.78.90.137;10.78.90.149
I'd like to place these two IP addresses in a bash array. To achieve that I tried the following:
n=$(grep -i ipaddress /opt/ipfile | cut -d'=' -f2 | tr ';' ' ')
This results in extracting the values alright but for some reason the size of the array is returned as 1 and I notice that both the values are identified as the first element in the array. That is
echo ${n[0]}
returns
10.78.90.137 10.78.90.149
How do I fix this?
Thanks for the help!
do you really need an array
bash
$ ipAddress="10.78.90.137;10.78.90.149"
$ IFS=";"
$ set -- $ipAddress
$ echo $1
10.78.90.137
$ echo $2
10.78.90.149
$ unset IFS
$ echo $# #this is "array"
if you want to put into array
$ a=( $# )
$ echo ${a[0]}
10.78.90.137
$ echo ${a[1]}
10.78.90.149
#OP, regarding your method: set your IFS to a space
$ IFS=" "
$ n=( $(grep -i ipaddress file | cut -d'=' -f2 | tr ';' ' ' | sed 's/"//g' ) )
$ echo ${n[1]}
10.78.90.149
$ echo ${n[0]}
10.78.90.137
$ unset IFS
Also, there is no need to use so many tools. you can just use awk, or simply the bash shell
#!/bin/bash
declare -a arr
while IFS="=" read -r caption addresses
do
case "$caption" in
ipAddress*)
addresses=${addresses//[\"]/}
arr=( ${arr[#]} ${addresses//;/ } )
esac
done < "file"
echo ${arr[#]}
output
$ more file
foo
bar
ipAddress="10.78.91.138;10.78.90.150;10.77.1.101"
foo1
ipAddress="10.78.90.137;10.78.90.149"
bar1
$./shell.sh
10.78.91.138 10.78.90.150 10.77.1.101 10.78.90.137 10.78.90.149
gawk
$ n=( $(gawk -F"=" '/ipAddress/{gsub(/\"/,"",$2);gsub(/;/," ",$2) ;printf $2" "}' file) )
$ echo ${n[#]}
10.78.91.138 10.78.90.150 10.77.1.101 10.78.90.137 10.78.90.149
This one works:
n=(`grep -i ipaddress filename | cut -d"=" -f2 | tr ';' ' '`)
EDIT: (improved, nestable version as per Dennis)
n=($(grep -i ipaddress filename | cut -d"=" -f2 | tr ';' ' '))
A variation on a theme:
$ line=$(grep -i ipaddress /opt/ipfile)
$ saveIFS="$IFS" # always save it and put it back to be safe
$ IFS="=;"
$ n=($line)
$ IFS="$saveIFS"
$ echo ${n[0]}
ipAddress
$ echo ${n[1]}
10.78.90.137
$ echo ${n[2]}
10.78.90.149
If the file has no other contents, you may not need the grep and you could read in the whole file.
$ saveIFS="$IFS"
$ IFS="=;"
$ n=$(</opt/ipfile)
$ IFS="$saveIFS"
A Perl solution:
n=($(perl -ne 's/ipAddress=(.*);/$1 / && print' filename))
which tests for and removes the unwanted characters in one operation.
You can do this by using IFS in bash.
First read the first line from file.
Seoncd convert that to an array with = as delimeter.
Third convert the value to an array with ; as delimeter.
Thats it !!!
#!/bin/bash
IFS='\n' read -r lstr < "a.txt"
IFS='=' read -r -a lstr_arr <<< $lstr
IFS=';' read -r -a ip_arr <<< ${lstr_arr[1]}
echo ${ip_arr[0]}
echo ${ip_arr[1]}

Resources