Skip to content

Instantly share code, notes, and snippets.

@eliranmal
Created July 27, 2026 18:32
Show Gist options
  • Select an option

  • Save eliranmal/869cda4a5989cefd5a64d42c2a9300b9 to your computer and use it in GitHub Desktop.

Select an option

Save eliranmal/869cda4a5989cefd5a64d42c2a9300b9 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
usage() {
echo "
USAGE
./sh-kebab.sh [path] [--dry-run] [--skip-dirs] [--copy-files]
OVERVIEW
this command renames all files in the current directory (or of the passed path), to the kebab-case naming convention.
OPTIONS
--dry-run
only print out file names, without renaming any files on the system.
--skip-dirs
avoid renaming directories.
--copy-files
make file copies with kebab-cased names, instead of overwriting existing files.
"
}
main() {
local temp_dir
local work_dir="$( pwd -P )"
temp_dir="$(create_temp_dir)"
cleanup_dir_on_exit "$temp_dir"
greet_on_exit
greet
cmd_rename_files "$work_dir" "$temp_dir" "$@"
}
cmd_rename_files() {
local is_dry_run
local file
local file_kebab
local work_dir="$1"
local temp_dir="$2"
if is_dry_run "$@"; then
log_ln 'listing file names in ['"$work_dir"']'
else
log_ln 'updating file names in ['"$work_dir"']'
fi
if is_dry_run "$@" || confirm_overwrite; then
for path in "$work_dir"/*; do
if should_skip_dirs "$@" && [[ -d "$path" ]]; then
continue
fi
file="$(basename "$path")"
file_kebab="$(kebabify "$file")"
if is_dry_run "$@"; then
log_file_data "$file" "$file_kebab"
else
if should_copy_files "$@"; then
log 'copying...'
log "source: $path"
# log "dest: $(dirname "$path")/$file_kebab"
# cp -pvR "$path" "$(dirname "$path")/sh-kebab-copy/$file_kebab"
else
log 'renaming...'
# mv -- "$f" "${f%.backup}"
fi
fi
done
fi
}
has_param() {
local term="$1"
shift
for arg; do
if [[ $arg == "$term" ]]; then
return 0
fi
done
return 1
}
is_dry_run() {
has_param '--dry-run' "$@"
}
should_skip_dirs() {
has_param '--skip-dirs' "$@"
}
should_copy_files() {
has_param '--copy-files' "$@"
}
confirm_overwrite() {
prompt_confirm 'this will overwrite any existing files.'
}
log_file_data() {
local file="$1"
local file_kebab="$2"
log "$file --> $file_kebab"
if ! validateFilename "$file_kebab"; then
log 'file name is invalid!'
fi
}
kebabify() {
echo "$1" | fill_with_dashes | camel_case_to_kebab_case
}
fill_with_dashes() {
local term="$(cat -)"
term="${term//_/-}"
term="${term//[[:blank:]]/-}"
# term="${term//[[:upper:]]+/\%}"
echo "$term"
}
camel_case_to_kebab_case() {
# - match uppercase letters that are not prefixed by a dash or another uppercase letter
# - prefix them with dashes
# - convert all uppercase letters into lowercase
cat - | sed -E 's/([^-[[:upper:]]])([[:upper:]])/\1-\2/g;s/^-//' | tr '[:upper:]' '[:lower:]'
# cat - | sed -E 's/[[:upper:]]/-&/g;s/^-//' | tr '[:upper:]' '[:lower:]'
}
# a simple check to ensure no spaces or underscores remain in the filename
validateFilename() {
! [[ $1 =~ [[:blank:]]|_ ]]
}
create_temp_dir() {
mktemp -d 2>/dev/null || mktemp -d -t 'sh-kebab'
}
cleanup_dir_on_exit() {
trap 'rm -rf '"$@"' >/dev/null 2>&1' EXIT
}
greet() {
log
log_ln 'hi :)'
}
greet_on_exit() {
trap 'log ; log_ln "bye bye!"' EXIT
}
prompt_confirm() {
local message="$1"
read -p "$(log "> $message continue? (y/n) ")" -r
[[ $REPLY =~ ^[Yy]$ ]]
}
log() {
echo " $1"
}
log_ln() {
echo "$(log "$1")
"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment