5

I am trying to capitalize each first letter of the first word in each sentence from a txt file called input.txt and I want this input file to be a argument of the shell script

 ./script.sh input.txt

sample input file:

i am Andrew. you are Jhon. here we are, forever.

result file:

I am Andrew. You are Jhon. Here we are, forever.

A special case. What if our text is (related to @RaduRadeanu answer)

i am andrew. you
are jhon. here we are
forever

the result would be:

I am andrew. You
Are jhon. Here we are
Forever.

So it convert to uppercase each first word of each sentence and also each first word of new line. How do we skip over uppercase first word of new line?

So the correct result must be:

I am andrew. You
are jhon. Here we are
forever.

What if the sentence closes in "?" or "!" ???

2
  • You may want to read through this: stackoverflow.com/questions/2264428/… and then consider adding a regular expression to search for the period-space to determine where to modify case.
    – freecode
    Aug 19, 2013 at 15:00
  • Since you mentioned in the comments that you are using vim, would an answer that works within vim, rather than a shell script, be useful?
    – evilsoup
    Aug 20, 2013 at 7:50

2 Answers 2

4

sed command is very powerful to edit files from shell scripts. With its help you can edit however you want a text file. These being said, the following script can do what you wish:

#!/bin/bash

#check if a file is given as argument
if [ $# -ne 1 ];then
  echo "Usage: `basename $0` FILE NAME"
  exit 1
fi

sed -i 's/^\s*./\U&\E/g' $@         #capitalize first letter from a paragraf/new line
sed -i 's/[\.!?]\s*./\U&\E/g' $@    #capitalize all letters that follow a dot, ? or !

For your special case, things became slightly:

#!/bin/bash

#check if a file is given as argument
if [ $# -ne 1 ];then
  echo "Usage: `basename $0` FILE NAME"
  exit 1
fi

sed -i '1s/^\s*./\U&\E/g' $@  #capitalize first letter from the file
sed -i 's/\.\s*./\U&\E/g' $@  #capitalize all letters that follow a dot

#check if the a line ends in dot, ? or ! character and 
#if yes capitalize first letter from the next line
next_line=0
cat $@ | while read line ;do
  next_line=$[$next_line+1]
  lastchr=${line#${line%?}}
  if [ "$lastchr" = "." ] || [ "$lastchr" = "!" ] || [ "$lastchr" = "?" ]; then
    sed -i "$[$next_line+1]s/^\s*./\U&\E/g" $@
  fi
done

Also, you can consult this tutorial: Unix - Regular Expressions with SED to see how to work in these situations.

5
  • @RaduRadeanu this works great. If you have time can you please explain it to me. I will edit the question, if you want to take a look over again. Aug 19, 2013 at 15:09
  • @RaduRadeanu take a look over my question. I've edit it. Aug 19, 2013 at 15:16
  • @RaduRadeanu We should cut the following: *sed -i 's/^\s./\U&\E/g' $@ #capitalize first letter from a paragraf/new line **, right ? Aug 19, 2013 at 15:18
  • 1
    @AdrianGeorge You must to put a simple condition to check if in the previous line the last character was a dot. Aug 19, 2013 at 15:23
  • @AdrianGeorge See the new edits. Aug 19, 2013 at 21:12
4

How about using bash's builtin 'read' function with the period character as delimiter to read each whole sentence into a variable, and then capitalizing the initial character of the variable? Something like

$ cat myfile
i am andrew. you
are jhon. here we are
forever.

$ while read -rd\. sntc; do printf "%s. " "${sntc^}"; done < myfile; printf "\n"
I am andrew. You
are jhon. Here we are
forever.

To handle multiple sentence terminators e.g. ? and ! as well as the regular period, here is a different approach using 'awk' - note that the RT variable that allows us to recover the particular record terminator which matched a particular sentence is an extension that may not be available in all varieties of 'awk'

$ cat myfile
i am andrew? you
are jhon. here we are
forever!

$ awk 'BEGIN{RS="[.!?]+[ \t\n]*"}; {sub(".", substr(toupper($0), 1,1), $0); printf ("%s%s", $0, RT)}' myfile
I am andrew? You
are jhon. Here we are
forever!

Note that the record separator regex above will handle multiple consecutive delimiters ('!?!!!') and optional trailing spaces - which the read-based version doesn't.

As a further enhancement, let's try to add rudimentary handling of quoted sentences by modifying the RS regex once more and changing the sub so that it upper-cases the first non-quote character:

awk 'BEGIN{RS="[.!?]+[\"'\'']?[ \t\n]*"}; {match($0, "[^\"'\'']"); sub("[^\"'\'']", substr(toupper($0),RSTART,1), $0); printf ("%s%s", $0, RT)}'

e.g.

$ cat myfile
i am andrew.    "are
you jhon?"  'here we are
forever!?'

$ awk 'BEGIN{RS="[.!?]+[\"'\'']?[ \t\n]*"}; {match($0, "[^\"'\'']"); sub("[^\"'\'']", substr(toupper($0),RSTART,1), $0); printf ("%s%s", $0, RT)}' myfile
I am andrew.    "Are
you jhon?"  'Here we are
forever!?'
6
  • That's a very neat solution. The only issue is that it will strip leading and trailing spaces, which may or may not be a problem.
    – evilsoup
    Aug 19, 2013 at 17:57
  • Yes I couldn't think of a way around that, except for explicitly adding a single trailing space (which may not correctly reflect the original spacing) in the printf i.e. "%s ", as shown Aug 19, 2013 at 18:00
  • @steeldriver What if our sentence closes in "?" or "!" ? And how can I overwrite the input.txt file with the result (output) ? Aug 19, 2013 at 20:50
  • 1
    @AdrianGeorge you could create a new file by surrounding the whole thing in brackets and putting a > at the end, like (while read ... done < input.txt; printf '\n') > output.txt, and then mv output.txt input.txt. The !/? problem is one that the other answer also suffers from. It's not an insurmountable problem, but extending either of these answers to cover those cases will bloat them. I think you'd be better off using a decent text editor (i.e. one with macro support, like vim or emacs).
    – evilsoup
    Aug 19, 2013 at 21:25
  • @evilsoup I am actually using vim. Aug 19, 2013 at 21:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .