0

It would be best if I could just use the rename command. But I think we have to use two regex.

The sed command that is working is

% echo MyExPression | sed --expression 's/\([A-Z]\)/-\L\1/g' --expression 's/^-//'                         
my-ex-pression
% echo myExPression | sed --expression 's/\([A-Z]\)/-\L\1/g' --expression 's/^-//'
my-ex-pression

I figured out that we can use

for file in ./* ; do mv "$file" "$(echo $file|sed -e 's/\([A-Z]\)/-\L\1/g' -e 's/^.\/-//')" ; done

But this command has multiple problems.

  1. It operates on both files and directories. I want to rename directories only
  2. It does not loop recursively.
  3. If kebab case filename is already there then it says mv: cannot move './first-folder-to-rename' to a subdirectory of itself, './first-folder-to-rename/first-folder-to-rename'

So, what might be the solution here?

Update 1

Here is a sample directory structure

% tree
.
├── EmptyFile.txt
├── FirstDirectoryName
│   ├── FourthDirectoryName
│   ├── secondDirectoryName
│   └── thirdDirectoryName
├── FourthDirectoryName
├── secondDirectoryName
└── thirdDirectoryName

Expected Output

% tree
.
├── EmptyFile.txt
├── first-directory-name
│   ├── fourth-directory-name
│   ├── second-directory-name
│   └── third-directory-name
├── fourth-directory-name
├── second-directory-name
└── third-directory-name
Ahmad Ismail
  • 11,636
  • 6
  • 52
  • 87
  • 1
    Please add sample filenames (no descriptions, no images, no links) and your desired filenames for that sample input to your question (no comment). – Cyrus Jul 11 '20 at 05:04
  • This might help with GNU sed: `sed 's/[A-Z]/-\L&/g; s/^-//'` – Cyrus Jul 11 '20 at 05:36

3 Answers3

1

zsh:

autoload -Uz zmv

zmv -n -Q '**/|*[[:upper:]]*(/od)' \
'${(M)f##*/}${(L)${${f##*/}//(#b)([[:upper:]])/-$match[1]}#-}'

remove -n if output is correct.

  • 1
    Adjust the glob qualifiers: `(.od)` for plain files only; `(od)` for any type. Check `man zshexpn` section *Glob Qualifiers* for many other possibilities. `od` sorting is required so that most nested are processed first (like `-depth` with `find`). –  Jul 11 '20 at 06:01
  • 1
    @blueray `$f` is the filename matched by `zmv`. Several modifications can be applied (as zsh can nest parameter expansions). Starting with innermost `##*/` to remove parent path (if any). Then a global substitution (with backreference enabled by `(#b)`) to add a hyphen before each uppercase letter. The outermost expansion lowercases the result and removes single leading hyphen. An improvement would be: `${(L)${f##*/}//(#b)(?)([[:upper:]])/$match[1]-$match[2]}`. A separate `##*/` with the `M` flag is added to match any existing parent path. –  Jul 11 '20 at 08:36
  • 1
    `(?)([[:upper:]])` won't handle repeated uppercase letters. E.g. `IsIBad` --> `is-ibad` –  Jul 11 '20 at 08:52
  • 1
    @blueray `**/|*` was a bad idea. Use `(**/)(*[[:upper:]]*)(/)`, and change second arg to `${1}${(L)${2//(#b)([[:upper:]])/-$match[1]}#-}`. `**/` for recursion. Capture both dirname and basename as `$1` & `$2`. `(/)` is the only qualifier needed as `od` is already in the [zmv script](https://github.com/zsh-users/zsh/blob/master/Functions/Misc/zmv#L225). Sorry for posting bad answer. –  Jul 11 '20 at 13:21
1

Using bash and GNU sed:

$ cat rendirs

#!/bin/bash

# function for renaming directories recursively
rendirrec () {
    local from to
    for from in *; do
        [[ -d $from && ! -h $from ]] || continue
        to=$(sed -E 's/([^A-Z])([A-Z])/\1-\2/g; s/.*/\L&/' <<< "$from")
        cd "$from" && { rendirrec; cd ..; } || exit
        [[ $to = "$from" ]] && continue
        if [[ -e $to ]]; then
            printf "'%s' already exists. Skipping..\n" "$to" >&2
            continue;
        fi
        mv "$from" "$to" || exit
    done
}

[[ -z $1 ]] && { echo "Missing argument"  >&2; exit 1; }
cd "$1" && rendirrec

Run as
$ ./rendirs top_of_the_directory_tree_to_be_renamed

M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17
1

find + shell + sed + tr:

find . -depth -type d -name '*[[:upper:]]*' -exec sh -c '
for i do
  parent=${i%/*}
  [ "$parent" = "$i" ] && parent="" || parent="$parent/"
  newname=$(sed '\''s/\(.\)\([[:upper:]]\)/\1-\2/g'\'' <<X | tr "[:upper:]" "[:lower:]"
${i##*/}
X
)
  echo mv -- "$i" "$parent$newname"
done' _ {} +
  • 1
    `s/\(.\)\([[:upper:]]\)/\1-\2/g` won't handle repeat uppercase letters correctly. –  Jul 11 '20 at 08:55