Loop through a file with colon-separated strings - bash

I have a file that looks like this:
work:week:day:england:
work1:week:day:sweden:
work2:week:day::
.....
Each time I loop through the list I want go get each string as a variable which I can use.
E.g if I want to know which location I work in I would get the fourth location column from the first column "work*"
I tried this:
for country in $( awk -F '[:]' '{print $1}' file.txt); do
if [[ "$country" == "england" ]];
then
echo "This user works in England!"
else
echo "You do not work in England!"
fi
done
I would like to get each strings separated by a colon as a variable for each row each loop.

You can use just bash for this: set IFS (internal field separator) to : and this will catch the fields properly:
while IFS=":" read -r a b c country
do
echo "$country"
done < "file"
This returns:
england
sweden
This way you will be able to use $a for the first field, $b for the second, $c for the third and $country for the forth. Of course, adapt the number and names to your requirements.
All together:
while IFS=":" read a b c country
do
if [[ "$country" == "england" ]]; then
echo "this user works in England"
else
echo "You do not work in England"
fi
done < "file"

Just do the whole thing in awk:
awk -F: '$4=="england"{print "this user works in England";next}
{print "You do not work in England"}' file
Set the field separator to a colon. If the fourth field is "england", print the first message. next skips to the next line. Otherwise, print the second message.
The fields on each line are accessible by $1, $2, etc. so you can use the data in each field within awk to do whatever you want. The field is read line by line automatically, so there's no need to write your own while read loop.

Related

How do i compare a given string with multiple lines of text in bash?

What i wanna do is assign the 3rd field (each field is separated by :) from each line in Nurses.txt to a variable and compare it with another string which is manually given by the user when he runs the script.
Nurses.txt has this content in it:
12345:Ana Correia:CSLisboa:0:1
98765:Joao Vieira:CSPorto:0:1
54321:Joana Pereira:CSSantarem:0:1
65432:Jorge Vaz:CSSetubal:0:1
76543:Diana Almeida:CSLeiria:0:1
87654:Diogo Cruz:CSBraga:0:1
32198:Bernardo Pato:CSBraganca:0:1
21654:Maria Mendes:CSBeja:0:1
88888:Alice Silva:CSEvora:0:1
96966:Gustavo Carvalho:CSFaro:0:1
And this is the script I have so far, add_nurses.sh:
#!/bin/bash
CS=$(awk -F "[:]" '{print $3}' nurses.txt)
if [["$CS" == "$3"]] ;
then
echo "Error. There is already a nurse registered in that zone";
else
echo "There are no nurses registered in that zone";
fi
When I try to run the script and give it some arguments as shown here:
./add_nurses "Ana Correia" 12345 "CSLisboa" 0
It´s supposed to return "Error. There is already a nurse registered in that zone" but instead it just tells me i have an Output error in Line #6...
A simpler and shorter way to do this job is
if grep -q "^[^:]*:[^:]*:$3:" nurses.txt; then
echo "Error. There is already a nurse registered in that zone"
else
echo "There are no nurses registered in that zone"
fi
The grep call can be simplified as grep -Fq ":$3:" if there is no risk of collision with other fields.
Alternatively, in pure bash without using any external command line utilities:
#!/bin/bash
while IFS=: read -r id name region rest && [[ $region != "$3" ]]; do
:
done < nurses.txt
if [[ $region = "$3" ]]; then
echo "Error. There is already a nurse registered in that zone"
else
echo "There are no nurses registered in that zone"
fi
An alternative way to read the colon separated file would not need awk at all, just bash built-in commands:
read to read from a file into variables
with the -r option to prevent backslash interpretation
IFS as Internal Field Separator to specify the colon : as field separator
#!/bin/bash
# parse parameters to variables
set add_nurse=$1
set add_id=$2
set add_zone=$3
# read colon separated file
set IFS=":"
while read -r nurse id zone d1 d2; do
echo "Nurse: $nurse (ID $id)" "Registered Zone: $zone" "$d1" "$d2"
if [ "$nurse" == "$add_nurse" ] ; then
echo "Found specified nurse '$add_nurse' already registered for zone '$zone'.'"
exit 1
fi
if [ "$zone" == "$add_zone" ] ; then
echo "Found another nurse '$nurse' already registered for specified zone '$add_zone'.'"
exit 1
fi
done < nurses.txt
# reset IFS to default: space, tab, newline
unset IFS
# no records found matching nurse or zone
echo "No nurse is registered for specified zone."
See also:
bash - Read cells in csv file - Unix & Linux Stack Exchange
Judging by the user input (by field from the nurses.txt) to determine if there is indeed a nurse in a given zone according to the op's description, I came up with this solution.
#!/usr/bin/env bash
user_input=("$#")
mapfile -t text_input < <(awk -F':' '{print $2, $1, $3, $4}' nurses.txt)
pattern_from_text_input=$(IFS='|'; printf '%s' "#(${text_input[*]})")
if [[ ${user_input[*]} == $pattern_from_text_input ]]; then
printf 'Error. There is already a nurse "%s" registered in that zone!' "$1" >&2
else
printf 'There are no nurse "%s" registered in that zone.' "$1"
fi
run the script with a debug flag -x e.g.
bash -x ./add_nurses ....
to see what the script is actually doing.
The script will work with the (given order) sample of arguments otherwise an option parser might be required.
It requires bash4+ version because of mapfile aka readarray. For completeness a while read loop and an array assignment is an alternative to mapfile.
while read -r lines; do
text_input+=("$lines")
done < <(awk -F':' '{print $2, $1, $3, $4}' nurses.txt)
First, the content of $CS is a list of items and not only one item so to compare the input against all the items you need to iterate over the fields. Otherwise, you will never get true for the condition.
Second [[ is not the correct command to use here, it will consider the content as bash commands and not as strings.
I updated your script, to make it work for the case you described above
#!/bin/bash
CS=$(awk -F "[:]" '{print $3}' nurses.txt)
for item in `echo $CS`
do
[ "$item" == "$3" ] && echo "Error. There is already a nurse registered in that zone" && exit 1
done
echo "There are no nurses registered in that zone";
Output
➜ $ ./add_nurses.sh "Ana Correia" 12345 "CSLisboa" 0
Error. There is already a nurse registered in that zone
➜ $ ./add_nurses.sh "Ana Correia" 12345 "CSLisboadd" 0
There are no nurses registered in that zone
As already stated in comments and answer:
use single brackets with space inside to test variables: [ "$CS" == "$3" ]
if using awk to get 3rd field of CSV file, it actually returns a column with multiple values as array: verify output by echo "$CS"
So you must use a loop to test each element of the array.
If you iterate over each value of the 3rd nurse's column you can apply almost the same if-test. Only difference are the consequences:
in the case when a value does not match you will continue with the next value
if a value matches you could leave the loop, also the bash-script
#!/bin/bash
# array declaration follows pattern: array=(elements)
CS_array=($(awk -F "[:]" '{print $3}' nurses.txt))
# view how the awk output looks: like an array ?!
echo "$CS_array"
# use a for-each loop to check each string-element of the array
for CS in "${CS_array[#]}" ;
do
# your existing id with corrected test brackets
if [ "$CS" == "$3" ] ;
then
echo "Error. There is already a nurse registered in that zone"
# exit to break the loop if a nurse was found
exit 1
# no else needed, only a 'not found' after all have looped without match
fi
done
echo "There are no nurses registered in that zone"
Notice how complicated the array was passed to the loop:
the "" (double quotes) around are used to get each element as string, even if containing spaces inside (like a nurse's name might)
the ${} (dollar curly-braces) enclosing an expression with more than just a variable name
the expression CS_array[#] will get each element ([#]) from the array (CS_array)
You could also experiment with the array (different attributes):
echo "${#CS_array[*]}" # size of array with prepended hash
echo "${CS_array[*]}" # word splitting based on $IFS
echo "${CS_array[0]}" # first element of the array, 0 based
Detailed tutorial on arrays in bash: A Complete Guide on How To Use Bash Arrays
See also:
Loop through an array of strings in Bash?

Using AWK for a variable inside a loop

So I get some URL's from my other team and I need to identiy a defined pattern in that URL and save the value after the pattern inside a variable.
Can this be achieved ?
**Input file: Just an example**
https://stackoverflow.com/questions/hakuna
https://stackoverflow.com/questions/simba
I wrote a simple for loop for this purpose
for i in `cat inputFile`
do
storeVal=awk -v $i -F"questions/" '{print$2}'
echo "The Name for the day is ${storeVal}"
length=`secondScript.sh ${storeVal}`
if [[ $length -gt 10 ]]
then
thirdScript.sh ${storeVal}
elif [[ $length -lt 10 ]]
then
fourthScript.sh ${storeVal}
else
echo "The length of for ${storeVal} is undefined"
done
Desired Output:
The Name for the day is hakuna
The length of for hakuna is greater than 10
Command1 hakuna executed
Command2 hakuna executed
The Name for the day is simba
Command1 simba executed
Command2 simba executed
And extra point to be noted.
The reason why I need to store the awk cut value in a variable is because I need to use that variable in multiple places with the loop.
Since it sounds like you want to run a command for every line in the input file, you can just use the built-in functionality of the shell:
while IFS=/ read -ra pieces; do
printf '%s\n' "${pieces[#]}" # prints each piece on a separate line
done < inputFile
If you always want the last part of the url (i.e. after the last /) on each line, then you can use "${pieces[-1]}":
while IFS=/ read -ra pieces; do
variable=${pieces[-1]} # do whatever you want with this variable
printf 'The Name for the day is %s\n' "$variable" # e.g. print it
done < inputFile
Since you want to extract all the string after the questions/ bit, you can simply use shell pattern substitution for that.
while IFS= read -r line; do
storeVal=${line##*questions/}
echo "The Name for the day is ${storeVal}"
done < 'inputFile'
Sample input file:
$ cat inputFile
https://stackoverflow.com/questions/hakuna
https://stackoverflow.com/questions/simba
https://stackoverflow.com/questions/simba/lion
Output of script:
The Name for the day is hakuna
The Name for the day is simba
The Name for the day is simba/lion

How to Print output of shell script in different columns of csv file

I have written a shell script and want to print output of 5 defined variables in csv file, I am using a condition, If condition 1 success then output should print in first 5 columns, else output should in next 5 columns, like below:
if condition 1 success
$DAY,$ModName,$Version,$END_TIME,$START_TIME
(should print in column number 1..5 of csv)
if condition 2 success
$DAY,$ModName,$Version,$END_TIME,$START_TIME
(should print in column number 6..10 of csv)
But using my code output always appends to next row
Below is my code:
if [ "$Version" = linux ]
then
echo "$DAY","$ModName","$Version","$END_TIME","$START_TIME" | awk -F "\"*,\"*" '{print $a ","}' >> output.csv;
else
echo "$DAY","$ModName","$Version","$END_TIME","$START_TIME" | awk -F "\"*,\"*" '{print $b}' >> output.csv;
fi
I tried n number of things apart from this code, but not able to find the solution.
I would appreciate your help :)
{print $6, $7, $8, $9, $10} refers to input fields, not output.
When you want to start with 5 empty fields just printf them (avoiding a \n)
if [ "${Version}" != "linux" ]; then
printf "%s" ",,,,,"
fi
echo "${DAY},${ModName},${Version},${END_TIME},${START_TIME}"
(Next time please use lowercase variable names)
When a variable can have a ',', you might need to give values in double quotes.

extract information from a file in unix using shell script

I have a below file which containing some data
name:Mark
age:23
salary:100
I want to read only name, age and assign to a variable in shell script
How I can achieve this thing
I am able to real all file data by using below script not a particular data
#!/bin/bash
file="/home/to/person.txt"
val=$(cat "$file")
echo $val
please suggest.
Rather than running multiple greps or bash loops, you could just run a single read that reads the output of a single invocation of awk:
read age salary name <<< $(awk -F: '/^age/{a=$2} /^salary/{s=$2} /^name/{n=$2} END{print a,s,n}' file)
Results
echo $age
23
echo $salary
100
echo $name
Mark
If the awk script sees an age, it sets a to the age. If it sees a salary , it sets s to the salary. If it sees a name, it sets n to the name. At the end of the input file, it outputs what it has seen for the read command to read.
Using grep : \K is part of perl regex. It acts as assertion and checks if text supplied left to it is present or not. IF present prints as per regex ignoring the text left to it.
name=$(grep -oP 'name:\K.*' person.txt)
age=$(grep -oP 'age:\K.*' person.txt)
salary=$(grep -oP 'salary:\K.*' person.txt)
Or using awk one liner ,this may break if the line containing extra : .
declare $(awk '{sub(/:/,"=")}1' person.txt )
Will result in following result:
sh-4.1$ echo $name
Mark
sh-4.1$ echo $age
23
sh-4.1$ echo $salary
100
You could try this
if your data is in a file: data.txt
name:vijay
age:23
salary:100
then you could use a script like this
#!/bin/bash
# read will read a line until it hits a record separator i.e. newline, at which
# point it will return true, and store the line in variable $REPLY
while read
do
if [[ $REPLY =~ ^name:.* || $REPLY =~ ^age:.* ]]
then
eval ${REPLY%:*}=${REPLY#*:} # strip suffix and prefix
fi
done < data.txt # read data.txt from STDIN into the while loop
echo $name
echo $age
output
vijay
23
well if you can store data in json or other similar formate it will be very easy to access complex data
data.json
{
"name":"vijay",
"salary":"100",
"age": 23
}
then you can use jq to parse json and get data easily
jq -r '.name' data.json
vijay

Grep for beginning of line while searching for a certain string

I have a file like such:
1 Role A
2 Role b
What I'd like to do is search for the String "Role A" and return the value 1 in a variable.
So something like the following:
if grep "$i" role_info.txt
then
<assign a variable the number associated with the string>
else
<no search string found - do something else>
fi
If the columns are delimited with tabs then you can do:
role='Role A'
number=$(awk -v role="$role" -F '\t' '$2==role {print $1}' role_info.txt)
If it's just spaces, try instead:
role='Role A'
number=$(grep "$role" role_info.txt | cut -d' ' -f1)
Either way, you can then check if a match was found with:
if [[ -n $number ]]; then
# number found
else
# not found
fi
Another option is:
while read number role; do
if [[ $role == 'Role A' ]]; then
# match found
fi
done < role_info.txt
This will be a bit more robust: the role has to be the second item on the line; it can't be in first position.
I use awk for this. Can I assume there's a tab between the number and the rolename column? If so,
awk -F'\t' '$2 == "Role A" {print $1}' role_info.txt
To take the pattern you're searching for from a shell variable gets a little tricky, because you're mixing shell variables and awk columns, but it can be done:
pat="Role A"
awk -F'\t' "\$2 == \"$pat\" {print \$1}" role_info.txt
Finally, to assign the result to another shell variable:
result=`awk -F'\t' "\\$2 == \"$pat\" {print \\$1}" role_info.txt`
(In this last case you need to double some of the backslashes inside ``.)
Here is the the simple way:
role=$(grep "Role A" foo.txt | awk '{print $1}')
Then to check if the role has been assigned, check if it's not empty like:
if [ ! -z "$role" ]; then
echo "Role exist"
else
echo "Role does not exist"
fi
You can do this:
read -a num <<< $(grep "Role A" role_info.txt) && echo ${num[0]}
Here num is an array, and ${num[0]} contains 1
Or this:
read num dummy <<< $(grep "Role A" role_info.txt) && echo $num
Here num contains 1
If no matches found, the variable num will be empty.
grep "Role A" role_info.txt >> /dev/null && var=1
You can use var value to decide.
How about this simple sed oneliner?
res=$(sed -ne "s/^\([0-9]\+\)[[:space:]]\+${i}/\1/p" role_info.txt)
[[ -z $res ]] && { <do something else>; }
How it works
The regular expression looks for a line like this:
[beginning of line][a number][at least 1 whitespace][value of variable i]
The number may have multiple digits. 0-leading numbers are also allowed.
The braces around the variable is redundant in this case, but I put it in case you want to switch to using something like an array in the future.
If there is a match, it is stored in the variable res. No match leads to an empty variable. The second line checks if the contents of res is of 0-length, if the compound command (the stuff inside braces) is executed. Please note, single line compound commands require you the have a space after the opening brace, and one before the closing brace. This is necessary to distinguish from brace expansion. Of course you can always break it into a multi-line command.
Note: The sed command needs to be in double quotes to allow for variable substitution. You should be careful not to pass quotes.

Resources