Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove the extension of a file?

I have a folder that is full of .bak files and some other files also. I need to remove the extension of all .bak files in that folder. How do I make a command which will accept a folder name and then remove the extension of all .bak files in that folder ?

Thanks.

like image 489
bashboy Avatar asked Dec 02 '22 22:12

bashboy


2 Answers

To remove a string from the end of a BASH variable, use the ${var%ending} syntax. It's one of a number of string manipulations available to you in BASH.

Use it like this:

# Run in the same directory as the files
for FILENAME in *.bak; do mv "$FILENAME" "${FILENAME%.bak}"; done

That works nicely as a one-liner, but you could also wrap it as a script to work in an arbitrary directory:

# If we're passed a parameter, cd into that directory. Otherwise, do nothing.
if [ -n "$1" ]; then
  cd "$1"
fi
for FILENAME in *.bak; do mv "$FILENAME" "${FILENAME%.bak}"; done

Note that while quoting your variables is almost always a good practice, the for FILENAME in *.bak is still dangerous if any of your filenames might contain spaces. Read David W.'s answer for a more-robust solution, and this document for alternative solutions.

like image 51
Jeff Bowman Avatar answered Dec 15 '22 01:12

Jeff Bowman


There are several ways to remove file suffixes:

In BASH and Kornshell, you can use the environment variable filtering. Search for ${parameter%word} in the BASH manpage for complete information. Basically, # is a left filter and % is a right filter. You can remember this because # is to the left of %.

If you use a double filter (i.e. ## or %%, you are trying to filter on the biggest match. If you have a single filter (i.e. # or %, you are trying to filter on the smallest match.

What matches is filtered out and you get the rest of the string:

file="this/is/my/file/name.txt"
echo ${file#*/}   #Matches is "this/` and will print out "is/my/file/name.txt"
echo ${file##*/}  #Matches "this/is/my/file/" and will print out "name.txt"
echo ${file%/*}   #Matches "/name.txt" and will print out "/this/is/my/file"
echo ${file%%/*}  #Matches "/is/my/file/name.txt" and will print out "this"

Notice this is a glob match and not a regular expression match!. If you want to remove a file suffix:

file_sans_ext=${file%.*}

The .* will match on the period and all characters after it. Since it is a single %, it will match on the smallest glob on the right side of the string. If the filter can't match anything, it the same as your original string.

You can verify a file suffix with something like this:

if [ "${file}" != "${file%.bak}" ]
then
    echo "$file is a type '.bak' file"
else
    echo "$file is not a type '.bak' file"
fi

Or you could do this:

file_suffix=$(file##*.}
echo "My file is a file '.$file_suffix'"

Note that this will remove the period of the file extension.

Next, we will loop:

find . -name "*.bak" -print0 | while read -d $'\0' file
do
    echo "mv '$file' '${file%.bak}'"
done | tee find.out

The find command finds the files you specify. The -print0 separates out the names of the files with a NUL symbol -- which is one of the few characters not allowed in a file name. The -d $\0means that your input separators are NUL symbols. See how nicely thefind -print0andread -d $'\0'` together?

You should almost never use the for file in $(*.bak) method. This will fail if the files have any white space in the name.

Notice that this command doesn't actually move any files. Instead, it produces a find.out file with a list of all the file renames. You should always do something like this when you do commands that operate on massive amounts of files just to be sure everything is fine.

Once you've determined that all the commands in find.out are correct, you can run it like a shell script:

$ bash find.out
like image 32
David W. Avatar answered Dec 15 '22 03:12

David W.