1

I have a list of folders and files whose names contain spaces. How can I change the names into camel case?

for oldname in *
do
  newname=`echo $oldname | sed -e 's/ /_/g'`
  if [ "$newname" = "$oldname" ]
  then
    continue
  fi
  if [ -e "$newname" ]
  then
    echo Skipping "$oldname", because "$newname" exists
  else
    mv "$oldname" "$newname"
  fi
done 

I have found this but it changes the spaces into underscores.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Amir Amara
  • 39
  • 9
  • Only in bash or a python script is accepted? – Bguess May 10 '22 at 08:38
  • I guess you can check [https://stackoverflow.com/questions/20742076/replace-uppercase-with-lowercase-letters](https://stackoverflow.com/questions/20742076/replace-uppercase-with-lowercase-letters) for a way to `sed` to camel case. For example : `echo "filename" | sed 's/^\(.\)\(.*\)$/\l\1\u\2/'`. If your filename "words" are separated by a space, it should be trivial. Maybe i should post an appropriate answer if not. – Zilog80 May 10 '22 at 09:52

3 Answers3

2

Try this Shellcheck-clean Bash code:

#! /bin/bash -p

lowers=abcdefghijklmnopqrstuvwxyz
uppers=ABCDEFGHIJKLMNOPQRSTUVWXYZ

for oldname in *; do
    [[ $oldname == *[[:space:]]* ]] || continue
    read -r -d '' -a parts <<<"$oldname"
    newname=''
    for p in "${parts[@]}"; do
        char1=${p:0:1}
        if [[ $lowers == *"$char1"* ]]; then
            tmp=${lowers%"$char1"*}
            uchar1=${uppers:${#tmp}:1}
            newname+=${uchar1}${p:1}
        else
            newname+=$p
        fi
    done
    if [[ -e $newname ]]; then
        printf "Skipping '%s', because '%s' exists\\n" "$oldname" "$newname" >&2
    else
        echo mv -v -- "$oldname" "$newname"
    fi
done
  • The code is intended to work with (the now ancient) Bash 3 because my understanding is that that is still the current version of the standard Bash on macOS. The code for uppercasing the first letter of filename parts is much more complicated than it would be with later versions of Bash (which have built-in mechanisms for case conversion). See How to convert a string to lower case in Bash? for information about changing case in various ways in various versions of Bash.
  • The code just prints the mv command that would be run. Remove the echo to make it actually do the mv.
  • See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I replaced echo with printf for the "Skipping" message.

For comparison, this is Bash 4+ code:

#! /bin/bash -p

for oldname in *; do
    [[ $oldname == *[[:space:]]* ]] || continue
    read -r -d '' -a parts <<<"$oldname"
    newname=''
    for p in "${parts[@]}"; do
        newname+=${p^}
    done

    if [[ -e $newname ]]; then
        printf "Skipping '%s', because '%s' exists\\n" "$oldname" "$newname" >&2
    else
        echo mv -v -- "$oldname" "$newname"
    fi
done
pjh
  • 6,388
  • 2
  • 16
  • 17
  • 1
    As always, the problematic character is the newline; `read` will truncate the part after it. Well, it's not like you will ever see it in a filename but you can't rule out the possibility because in UNIX a filename can contain any character but the `/` and the `NULL-BYTE`. – Fravadona May 10 '22 at 12:56
  • 1
    @Fravadona, thanks for reporting the bug. I've tried to fix it by adding `-d ''` to the `read` options. The code still assumes that `IFS` has its default value. – pjh May 10 '22 at 13:17
1

You can use the regular expression aptitude to deal with upper and lower case translations, regarding your current local collation (LC_ALL, check with the locale command).

If your filename's "words" are separated with a space and are all in lower case, you can use a simple shell script like this :

#!/bin/sh
while read -r FILENAME ; do
  NEWNAME="`echo \"${FILENAME}\" | sed 's/  *\([^ ]\)/\u\1/g'`"
  if [ ! "${NEWNAME}" ] ; then
    NEWNAME="${FILENAME}";
  fi
  if [ "${FILENAME}" = "${NEWNAME}" ]; then
    printf "No change : %s\\n" "${FILENAME}" >&2;
  else
    if [ -e "${NEWNAME}" ] ; then
      printf "Already changed : %s => %s\\n" "${FILENAME}" "${NEWNAME}" >&2;
    else
      echo "mv \"${FILENAME}\" \"${NEWNAME}\"";
    fi
  fi
done

Remove the echo on echo "mv \"${FILENAME}\" \"${NEWNAME}\""; to do the mv.

Note that it should work fine with accented letters or any unicode letter having lower and upper code.

The script takes the file list to operate from stdin, so to use it "as is", you can use something like the following examples :

  • find . -type 'f' | theScript.sh

    For a whole tree of files. For folders, you'll have to operate them separately. List them and sort them in a descending order.

  • ls -1 | theScript.sh

    For files in the current folder.

If your files may have all or partial upper cases at start and you look to force them entirely to camel case, you can change the line :

  NEWNAME="`echo \"${FILENAME}\" | sed 's/  *\([^ ]\)/\u\1/g'`"

With:

  NEWNAME="\`echo \"${FILENAME}\" | sed 's/\(.*\)/\l\1/;s/  *\([^ ]\)/\u\1/g'\`"
Zilog80
  • 2,534
  • 2
  • 15
  • 20
  • If i Would like to iterate this to every sub-folder inside the main directory this script runs in, then i need to change instead of FILENAME to " FILENAME in *"? – Amir Amara May 10 '22 at 19:24
  • @AmirAmara No, you just have to use `find . -type 'f' | theScript.sh` from the main folder. – Zilog80 May 10 '22 at 19:32
  • using this command outputs that the script is not there, `find . -type d -follow` this lists subfolders , tried to pipe it with this `xargs -I {} ./test.sh {}` didn't work for me :( – Amir Amara May 11 '22 at 13:45
  • @AmirAmara You have to put the provided script lines in a "theScript.sh" file and give it execution permission in order for the "find" command to work. – Zilog80 May 11 '22 at 18:10
  • that's what was done :'( – Amir Amara May 12 '22 at 07:43
  • @AmirAmara That's probably due to the fact that you have bash only on macOs. Change the first line of the script with `#!/bin/bash` instead of `#!/bin/sh`. – Zilog80 May 13 '22 at 19:23
  • tried this already unfortunately still having the same problem – Amir Amara May 15 '22 at 12:00
  • 1
    @AmirAmara In case you have copy&paste the lines with CRLF, try `dos2unix theScript.sh && chmod 774 theScript.sh;` from your macOs bash shell. If it does not work, you'll have to change the second line with `find -type 'f'' | while read -r FILENAME ; do` and directly run theScript.sh from the target main folder. – Zilog80 May 16 '22 at 08:12
  • Does't work for me, it outputs commands like: `mv "./badge/badge.vue" "\`echo "./badge/badge.vue" | sed 's/\(.*\)/\l\1/;s/ *\([^ ]\)/\u\1/g'\`"` but when I run it it shows `mv: rename ./badge/badge.vue to l./badge/badge.vue: No such file or directory` – Vedmant Nov 04 '22 at 22:35
  • @Vedmant The provided script was for filenames *with spaces*. You should use `NEWNAME="\`echo \"${FILENAME}\" | sed 's/\(.*\)/\l\1/;s/\/\(.\)\([^\/]*\)$/\/\u\1\2/'\`"` in your case if your filenames include paths and no space. – Zilog80 Nov 06 '22 at 01:26
  • @Vedmant If you're looking for lowerCamelCase, you'll need to specify which word separator character is used. If there aren't any, going for lowerCamelCase will not be easy, you may have to use a word dictionary and a far more complex script. – Zilog80 Nov 06 '22 at 11:27
  • @Zilog80 My requirement was to transform filenames like 'file-name', 'file name', 'file_name' to 'FileName', but I already just did it with node script. – Vedmant Nov 06 '22 at 18:21
0

If you have rename installed, then all you need to do is :

rename 's/ /_/g' *
Philippe
  • 20,025
  • 2
  • 23
  • 32