13

I'm trying to write a shell command where I can specify a directory, and then every file and directory inside will have the first letter of every word capitalized. So

/doCumenTS/tesT.txt

should change to

/DoCumenTS/TesT.txt

I'm thinking it should start like

for i in directory do
tr something_goes_here
done

The problem I can't figure out is how to only do the first letter. I've made a script that uppercase the whole file name, but I can't figure out how to only get the first letter of every word.

Thanks!

Levon
  • 138,105
  • 33
  • 200
  • 191
user1507982
  • 131
  • 1
  • 1
  • 3
  • There is no way to restrict the context of `tr`, so it is definitely the wrong command here. It always substitutes all occurrences of the characters you supply as arguments. – tripleee Jul 07 '12 at 09:41
  • This could help: http://theunixshell.blogspot.com/2013/02/capitalize-every-first-letter-of-word.html – Vijay May 02 '13 at 09:19

6 Answers6

7

Using GNU Sed

You can do this quite easily with GNU sed. For example:

$ echo '/doCumenTS/tesT.txt' | sed 's!/.!\U&!g'
/DoCumenTS/TesT.txt

Limitations

Note that the \U escape is a GNU sed extension. The manual says:

Finally, as a GNU 'sed' extension, you can include a special sequence made of a backslash and one of the letters 'L', 'l', 'U', 'u', or 'E'.

`\U'
     Turn the replacement to uppercase until a `\L' or `\E' is found
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
7

Bash 4.0+ has a parameter substitution 'lowercase to uppercase'. To change only the first character use it like this:

list=( 'aaa/bbb' 'aAAA' 'aaa/bbb/ccc' 'aaa bbb/ccc' )

IFS='/'
for item in "${list[@]}"; do
  split=( $item )
  echo "'${split[*]^}'"
done

The result:

'Aaa/Bbb'
'AAAA'
'Aaa/Bbb/Ccc'
'Aaa bbb/Ccc'
Fritz G. Mehner
  • 16,550
  • 2
  • 34
  • 41
  • This doesn't help the OPs use case of capitalizing after each path component. If you can show how to do the uppercase expansion on multiple parts of the pathnames using pure Bash, then it would be a good answer, but I couldn't find a way to make that happen either without multiple passes over the string. – Todd A. Jacobs Jul 07 '12 at 09:09
  • You can do a second inner loop with `IFS=/` to split the pathname components. – tripleee Jul 07 '12 at 09:43
  • Changed after tripleee's comment. – Fritz G. Mehner Jul 07 '12 at 11:38
6
Source="ONE TWO THREE FOUR"
Target="One Two Three Four"

Solution is below:

Target=`echo $Source | tr [A-Z] [a-z] | sed -e 's/^./\U&/g; s/ ./\U&/g'`
echo $Target

One Two Three Four

Taken from here: http://souravgulati.webs.com/apps/forums/topics/show/8584862-shell-script-capitalize-first-letter-of-every-word-of-a-line-

Mat
  • 202,337
  • 40
  • 393
  • 406
Parvinder Singh
  • 123
  • 1
  • 2
  • Only works for characters A-Z, and not for international characters. – svante Mar 14 '18 at 13:50
  • Just what I was looking for! – bballdave025 Jun 07 '18 at 19:34
  • 1
    @svante, look at Unicode character decomposition/normalization, [here](https://www.unicode.org/reports/tr15/tr15-18.html#Decomposition) and [here](https://docs.python.org/2/library/unicodedata.html). [Decompose](https://stackoverflow.com/q/16467479/6505499), check for "bare latin letters", use this code, then [recombine](https://stackoverflow.com/q/446222/6505499). This isn't all `bash`, though one could write a `bash` version. – bballdave025 Jun 07 '18 at 19:35
  • Do note that this solution, while it capitalises the first character, forces all other characters to lowercase. Looking at the post, I don't think this is the requested outcome. – Mario Feb 27 '22 at 13:07
3

Before:

$ find . -mindepth 1 -depth -print
./file1
./bar/file3
./bar
./foo/file2
./foo/baz/file4
./foo/baz
./foo

Modification:

find . -mindepth 1 -depth -print0 | 
while IFS= read -r -d '' filename; do 
    b=$(basename "$filename")
    mv "$filename" "$(dirname "$filename")/${b^}"
done

After:

$ find . -mindepth 1 -depth -print
./Foo/Baz/File4
./Foo/Baz
./Foo/File2
./Foo
./Bar/File3
./Bar
./File1
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
2

Avoid the loop using features from v4:

input='/doCumenTS/tesT.txt'
IFS='/'
dirs=( $input )           # split on '/'
output=( "${dirs[*]^}" )  # capitalize
IFS=$' \t\n'              # reset IFS

You can embed it within a shell function:

capitalize_subdirs()
{
  local IFS='/'
  dirs=( $1 )
  echo "${dirs[*]^}"
}

Usage:

output=$( capitalize_subdirs '/doCumenTS/tesT.txt' )

This works even on path names containing spaces, tabs and new lines!

$ capitalize_subdirs '/doCumenTS/tesT.txt'
/DoCumenTS/TesT.txt

$ capitalize_subdirs $'/d\noCu\tme nTS/tesT.txt'
/D
oCu     me nTS/TesT.txt
oHo
  • 51,447
  • 27
  • 165
  • 200
0

Using rename, you can do this in the current directory with:

rename 's/./\U$&/' *; rename 's/ ./\U$&/g' *;

Use the -n option to first display the changes without executing them.

Philmore
  • 1
  • 1