0

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

  • Welcome to SO! Did you research this here or on bash-related sister sites [linux.se] and [ubuntu.se]? – hc_dev Mar 07 '21 at 20:31
  • 3
    The `[[` is a command it is a kind of `test` command but more flexible and has more feature and it needs a closing `]]` . A space is needed between the `[[` and the closing `]]` and strings inside it. See https://shellcheck.net for validating your script. – Jetchisel Mar 07 '21 at 20:34
  • Would help if you [edit] your question and post the __error message you got__. Something like".. is not a command" ? – hc_dev Mar 07 '21 at 20:48
  • 1
    Also you're trying to match field 3, the ones that starts with a capital C, but the first input you gave as argument to your script is the field 2 which is the name of the nurses. On top of that `$CS` is one whole string, you might consider using an array for the data type. There are a lot of things that needed to be done with your script. – Jetchisel Mar 07 '21 at 21:15

5 Answers5

1

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
M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17
  • Actually, if we assume that nurse names (two words at least) and zone names (one word) are different, i.e. no nurse is named like a zone, a `grep -Fq ":$3:" nurses.txt` should do as well. This would also allow for zone names having special characters (a dot for instance). – user1934428 Mar 08 '21 at 08:53
  • @user1934428 Updated the answer to reflect this fact. – M. Nejat Aydin Mar 08 '21 at 10:53
  • @hc_dev I fail to see what your point is. The code will just work as intended when the `$region` has embedded blanks. – M. Nejat Aydin Mar 08 '21 at 23:09
  • Explanation would help, some [compare strings wrapped in quotes](https://stackoverflow.com/questions/2237080/how-to-compare-strings-in-bash), others don't. Robustness? – hc_dev Mar 08 '21 at 23:15
  • @hc_dev Still I fail to see how the script isn't robust. – M. Nejat Aydin Mar 08 '21 at 23:29
0

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)
Jetchisel
  • 7,493
  • 2
  • 19
  • 18
  • Elegant declaration of parameter array `user-input` Found an explanatory [question about awk, arrays, readarray/mapfile](https://stackoverflow.com/questions/15105135/bash-capturing-output-of-awk-into-array) – hc_dev Mar 07 '21 at 23:23
0

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

hc_dev
  • 8,389
  • 1
  • 26
  • 38
-1

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
Al-waleed Shihadeh
  • 2,697
  • 2
  • 8
  • 22
  • Your syntax has very mixed style and skill-level: redundant `echo` in iterator, advanced short-circuit in iteration. The basic if-then-else syntax is gone. Anything left of asker's script to build-on? – hc_dev Mar 07 '21 at 21:07
  • 2
    What do yo mean with "`[[` [...] consider the content as bash commands and not as strings"? – Benjamin W. Mar 07 '21 at 21:23
-1

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?

hc_dev
  • 8,389
  • 1
  • 26
  • 38