2

Write Shell script to split the below file into two files male_nominee.txt and female_nominee.txt based on gender. If file male_nominee.txt or female_nominee.txt already exists, then append the content.

Display the contents of female_nominee.txt and male_nominee.txt

names.txt

23|Arjun|Male

24|Akshara|Female

17|Aman|Male

19|Simran|Female

My code:

while IFS= read -r line;
do
    if i=$(grep "Male" names.txt)
    then
        echo "$line" >> male_nominee.txt
    fi
    if j=$(grep "Female" names.txt)
    then
        echo "$line" >> female_nominee.txt
    fi
done < "names.txt"
ls
cat male_nominee.txt
cat female_nominee.txt

In my output, I have the contents of names.txt in both my files. Can someone help me with this problem?

Greenonline
  • 1,851
  • 7
  • 17
  • 23
  • Classes usually have some pretty terrible assignments but this is one of the worst ones I've seen. – jesse_b Aug 20 '20 at 18:37

5 Answers5

1

The "Display the contents of female_nominee.txt and male_nominee.txt" requirement is sort of unclear and IMO has no place in the script but I've included it anyway. You should generally avoid reading files with a while read loop, and since this is a delimited file it is easily managed with awk:

#!/usr/bin/env sh

infile=./names.txt

awk -F| '$3 == "Male"' "$infile" >> male_nominee.txt awk -F| '$3 == "Female"' "$infile" >> female_nominee.txt

cat male_nominee.txt female_nominee.txt


Additionally, some issues with your script:

Your if statements are grepping from names.txt instead of line since that file contains both Male and Female both conditions will pass every time.

There is no need to assign to a variable in each line, that variable is never being used. You could just do if echo "$line" | grep -q 'Male'; then

You don't need two if statements since it will be if/else

if echo "$line" | grep -q 'Male'; then
  echo "$line" >>male_nominee.txt
else
  echo "$line" >>female_nominee.txt
fi
jesse_b
  • 37,005
1

A few more variations:

awk — single pass

awk -F'|' '
   $3 == "Male"   { print >> "male_nominee.txt"   }
   $3 == "Female" { print >> "female_nominee.txt" }
          ' names.txt

Similar to jesse_b’s answer, but it reads the file only once and does the I/O redirection within the awk script.  Note that these awk answers allow the data format to be modified; e.g.,

age|name|sex|height|weight|…

but they will ignore a line where there is a space between the second | and the sex.

bash

#!/bin/bash
while read line
do
        if [[ $line =~ Male$ ]]
        then
                printf '%s\n' "$line" >> male_nominee.txt
        fi
        if [[ $line =~ Female$ ]]
        then
                printf '%s\n' "$line" >> female_nominee.txt
        fi
done < names.txt

I guess this is what you were trying to do — read each line into the shell and test whether the sex is male or female.

  • jesse is right, in general: You should generally avoid reading files with a while read loop.  See Why is using a shell loop to process text considered bad practice?  But one of the drawbacks of using a shell loop to process text is that people often invoke an external utility on each iteration of the loop, and this example doesn’t do that.
  • Also, if you are given an artificial assignment to do something entirely in the shell, then you should follow the rules of the assignment.
  • This is more tolerant of spaces in the file, but does not allow additional data after the sex.
  • In bash, =~ compares a string to a regular expression.  In a regular expression, $ means the end, so $line =~ Male$ checks whether $line ends with Male.  If we just said $line =~ Male (without the $), then a woman whose name was Maleficent would be counted as a man.
  • If you’re worried about backslashes (\) in the data, use read -r instead of just read.
  • It probably doesn’t matter in this case (if every line begins with a number), but, in general, printf is safer than echo.

POSIX shell

#!/bin/sh
while read line
do
        case "$line" in
            (*Male)
                printf '%s\n' "$line" >> male_nominee.txt
                ;;
            (*Female)
                printf '%s\n' "$line" >> female_nominee.txt
                ;;
        esac
done < names.txt
  • This will be more portable than the bash version.
  • case is the traditional way of testing a string against pattern(s) in the shell.  It uses filename matching (i.e., glob) patterns instead of regular expressions.
  • A glob pattern must match, so we need to put a * before the sex values.  If we checked for Male (without the *), it would match only lines that were only the word Male (i.e., without an age and a name).  On the other hand, this means that we don’t need to put any marker on the end.
0

Your problem is that the statement

if i=$(grep "Male" names.txt)

will:

  1. Search "Male" in the whole names.txt
  2. Return the output ("all the lines containing Male") assigning that to variable i
  3. If the assignment was successful (should always be), execute the content of the if

As you are reading line by line, you probably want to check only that line.

You can do that with if echo "$line" | grep -q "Male" (or, if you want to avoid -q, which is not defined by POSIX, redirect the output to /dev/null)

Note that this will search for "Male" in the whole line, so it could fail if e.g. you had someone called "AMalek" in the file.

As you are reading line by line, rather than your read, you could use IFS="|" read -r age name gender and then just an if [ $var = "value" ];

Another option, using grep, would be to require a leading "|" (note it is a special character) and that it ends the line.

Note that in such case you could replace the whole loop with a couple of greps.

(the error with females is exactly the same as with males)

Ángel
  • 3,589
0
#!/bin/sh

 grep  "male$" names.txt | cat >> female_nominee.txt

 cat female_nominee.txt

 grep "Male" names.txt | cat >> male_nominee.txt

 cat male_nominee.txt
Kevdog777
  • 3,224
  • 1
    Welcome to the site, and thank you for your contribution. Would you mind explaining what this script does, and how? Why do you need to pipe the output of grep to cat, and redirect that to the files, instead of directly redirecting the output of grep? – AdminBee Aug 21 '20 at 08:07
0

Here's a simple solution using grep to filter and ">>" to append.

grep "Female$" names.txt >> female_nominee.txt
grep "Male$" names.txt >> male_nominee.txt

cat female_nominee.txt cat male_nominee.txt