rename all the files in the current directory whose name conatains upper-case into all lower case - shell

Iam trying a shell script which will rename all the files in the current directory whose name contains upper-case characters into all lower case. For example, if the directory contains a file whose name is CoUnt.c, it should be renamed to count.c.
for f in *;
do
if [ -f "$f" ]; then
tr 'A-Z' 'a-z'
fi
done
but it is not working.
is there is any better solution for this?

You are not passing any data into the tr program, and you are not capturing any output either.
If you are using sh:
for f in *[A-Z]*
do
if [ -f "$f" ]; then
new_name=$(echo "$f"|tr 'A-Z' 'a-z')
mv "$f" "$new_name"
fi
done
Note the indentation - it makes code easier to read.
If you are using bash there is no need to use an external program like tr, you can use bash expansion:
for f in *[A-Z]*
do
if [[ -f $f ]]; then
new_name=${f,,*}
mv "$f" "$new_name"
fi
done

The problem is tr accepts values from stdin. So in order to translate upper to lower in each filename, you could do something like:
#!/bin/sh
for f in *
do
[ -f "$f" ] || continue
flc=$(echo "$f" | tr 'A-Z' 'a-z') ## form lower-case name
[ "$f" != "$flc" ] && echo mv "$f" "$flc"
done
(note: remove the echo before mv to actually move the files after you are satisfied with the operation)

Since I am unable to add comment posting here,
Used sed and it works for me
#!/bin/bash
for i in *
do
if [ -f $i ]
then
kar=$(echo "$i" | sed 's/.*/ \L&/')
mv "$i" "$kar"
done

The following code works fine.
for f in *
do
if [ -f $f ]; then
echo "$f" | tr 'A-Z' 'a-z' >/dev/null
fi
done

I would recommend rename because it is simple, efficient and also will check for clashes when two different files resolve to the same result:
You can use it with a Perl regex:
rename 'y/A-Z/a-z/' *
Documentation and examples available here.

Related

Convert folder and file names to camel case

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.
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
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'\`"
If you have rename installed, then all you need to do is :
rename 's/ /_/g' *

bash : change part of filename to lowercase

I need to rename a list of files changing any file extension to lowercase:
ie: from My_TEST.ONE.two.Three.fOuR.FIve to My_TEST.one.two.three.four.five
At the moment the way I've found is this one
#!/bin/bash
sourcefilename="My_TEST.ONE.two.Three.fOuR.FIve"
newfilename=""
for word in $(echo $sourcefilename | tr '.' '\n'); do
if [ -z "$newfilename" ]; then
newfilename="$word"
else
newfilename="$newfilename.$(echo $word | tr [:upper:] [:lower:])"
fi
done
Is there a better (and maybe elegant) approach?
Use bash Parameter Expansion features.
fileName='My_TEST.ONE.two.Three.fOuR.FIve'
first="${fileName%%.*}"
rest="${fileName#*.}"
echo mv -v "${fileName}" "${first}.${rest,,[A-Z]}"

Add character to file name if duplicate when moving with bash

I currently use a bash script and PDFgrep to rename files to a certain structure. However, in order to stop overriding if the new file has a duplicate name, I want to add a number at the end of the name. Keep in mind that there may be 3 or 4 duplicate names. What's the best way to do this?
#!/bin/bash
if [ $# -ne 1 ]; then
echo Usage: Renamer file
exit 1
fi
f="$1"
id1=$(pdfgrep -m 1 -i "MR# : " "$f" | grep -oE "[M][0-9][0-9]+") || continue
id2=$(pdfgrep -m 1 -i "Visit#" "$f" | grep -oE "[V][0-9][0-9]+") || continue
{ read today; read dob; read dop; } < <(pdfgrep -i " " "$f" | grep -oE "[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]")
dobsi=$(echo $dob | sed -e 's/\//-/g')
dopsi=$(echo $dop | sed -e 's/\//-/g')
mv -- "$f" "${id1}_${id2}_$(printf "$dobsi")_$(printf "$dopsi")_1.pdf"
Use a loop that checks if the destination filename exists, and increments a counter if it does. Replace the mv line with this:
prefix="${id1}_{id2}_${dob}_${dop}"
counter=0
while true
do
if [ "$counter" -ne 0 ]
then target="${prefix}_${counter}.pdf"
else target="${prefix}.pdf"
fi
if [ ! -e "$target" ]
then
mv -- "$f" "$target"
break
fi
((counter++))
done
Note that this suffers from a TOCTTOU problem, if the duplicate file is created between the ! -f "$target" test and the mv. I thought it would be possible to replace the existence check with using mv -n; but while this won't overwrite the file, it still treats the mv as successful, so you can't test the result to see if you need to increment the counter.

Creating a bash script to change file names to lower case

I am trying to write a bash script that convert all file names to lowercase, but I have a problem because it does not work for one case.
When you have your file1 and FILE1, and you will use it on the FILE1 it will replace letters file1.
#!/bin/bash
testFILE=""
FLAG="1"
for FILE in *
do
testFILE=`echo FILE | tr '[A-Z]' '[a-z]'`
for FILE2 in *
do
if [ `echo $testFILE` = `echo $FILE2` ]
then
FLAG="0"
fi
done
if [ $FLAG = "1" ]
then
mv $FILE `echo $FILE | tr '[A-Z]' '[a-z]'`
fi
FLAG="1"
done
Looks like
testFILE=`echo FILE | tr '[A-Z]' '[a-z]'`
should be
testFILE=`echo "$FILE" | tr '[A-Z]' '[a-z]'`
Re-writing your script to fix some other minor things
#!/bin/bash
testFILE=
FLAG=1
for FILE in *; do
testFILE=$(tr '[A-Z]' '[a-z]' <<< "$FILE")
for FILE2 in *; do
if [ "$testFILE" = "$FILE2" ]; then
FLAG=0
fi
done
if [ $FLAG -eq 1 ]; then
mv -- "$FILE" "$(tr '[A-Z]' '[a-z]' <<< "$FILE")"
fi
FLAG=1
done
Quote variables to prevent word-splitting ("$FILE" instead of $FILE)
Generally preferable to use $() instead of tildes
Don't use string comparison where you don't have to
Use -- to delimit arguments in commands that accept it (in order to prevent files like -file from being treated as options)
By convention, you should really only use capital variable names for environment variables, though I kept them in above.
Pipes vs here strings (<<<) doesn't matter so much here, but <<< is slightly faster and generally safer.
Though more simply, I think you want
#!/bin/bash
for file in *; do
testFile=$(tr '[A-Z]' '[a-z]' <<< "$file")
[ -e "$testFile" ] || mv -- "$file" "$testFile"
done
Or on most modern mv implementations (not technically posix)
#!/bin/bash
for file in *; do
mv -n -- "$file" "$(tr '[A-Z]' '[a-z]' <<< "$file")"
done
From the man page
-n, --no-clobber
do not overwrite an existing file
Below script :
find . -mindepth 1 -maxdepth 1 -print0 | while read -d '' filename
do
if [ -e ${filename,,} ]
then
mv --backup ${filename} ${filename,,} 2>/dev/null
# create a backup of the desination only if the destination already exist
# suppressing the error caused by moving the file to itself
else
mv ${filename} ${filename,,}
fi
done
may do the job for you.
Advantages of this script
It will parse files containing newlines.
It avoids a prompt by doing selective backup destination that already exists.

(Bash) rename files but give it a new extension that will count up.. (md5sum)

I need to rename all files in a folder and give it a new file extension. I know how I can rename files with bash. The problem I have is, I need to rename it to:
file.01 file.02 file.03 and counting up for all files found.
Can somebody provide me an example where to start?
This is what i need:
md5sum * | sed 's/^\(\w*\)\s*\(.*\)/\2 \1/' | while read LINE; do
mv $LINE
done
but that doesnt give it an extension that will go from file.01 file.02 file.03 etc.
If one reads your requirements literally...
counter=0
for file in *; do
read sum _ <<<"$(md5sum "$file")"
printf -v file_new "%s.%02d" "$sum" "$counter"
mv -- "$file" "$file_new"
(( counter++ ))
done
This is less efficient than reading the filenames from md5sum's output, but more reliable, as globbing handles files with unusual names (newlines, special characters, etc) safely.
something line this:
i=0
for f in *
do
if [ -f $f ]; then
i=`expr $i + 1`
if [ $i -lt 10 ]; then
i=0$i
fi
sum=`md5sum $f | cut -d ' ' -f 1`
mv $f $sum.$i
fi
done

Resources