chop
chop

Reputation: 471

Using bash I need to remove trailing spaces before a file extension in file names

I have hundreds of files which look similar to:

"QDN34 Unit5 mark-up - Judy .pdf"  
"QDN34 Unit7 mark-up - Judy .pdf"  
"file with two character ext .ai"  
"file with dot. trailing space and no ext "  
"file with no ext"

Notice that there is a space at the end of all but the last, excluding file extensions where relevant.

I need to retain spaces within the filenames (not ideal) and remove those trailing spaces.
The result should be:

"QDN34 Unit5 mark-up - Judy.pdf"
"QDN34 Unit7 mark-up - Judy.pdf"  
"file with two character ext.ai"  
"file with dot. trailing space and no ext"  
"file with no ext"

So far I have:

newName=$(find . -type f -name \*\ .* | cut -d'.' -f2 | sed 's/\ $//' | sed 's/^\/*//')
extens=$(find . -type f -name \*\ .* | sed 's@.*/.*\.@.@')
oldName=$(find . -type f -iname \*\ .* | sed 's/^\.\/*//')
for f in "$oldName" ; do mv -nv "$oldName" "$newName""$extens" ; done

But I am getting errors which look like the indexes are not matching. It feels like I should be using an array, but I'm not sure how.

The output was:

mv: rename file with two character ext .ai  
QDN34 Unit5 mark-up - Judy .pdf  
QDN34 Unit7 mark-up - Judy .pdf to file with two character ext  
QDN34 Unit5 mark-up - Judy  
QDN34 Unit7 mark-up - Judy.ai  
.pdf  
.pdf: No such file or directory

Upvotes: 4

Views: 2818

Answers (4)

user4401178
user4401178

Reputation:

#!/bin/bash
IFS=$(echo -en "\n\b") && for a in $(ls -f1 *); do 
file_ext="${a##*.}"
if [[ ! -z "${file_ext}" && ${#file_ext} -lt 4 ]]; then 
 file_base="${a%.*}"
else 
 file_base="${a}" 
 file_ext=""
fi 
[ "${file_base:$((${#file_base}-1)):1}" = " " ] && \
 file_base="${file_base% *}"
new_file="${file_base}${file_ext:+.}${file_ext}" 
if [ ! "${new_file}" = "${a}" ]; then 
 echo "mv -nv \"$a\" \"${new_file}\"" 
 #mv -nv "${a}" "${file_base}.${file_ext}"
fi 
done 

You can run this bash script safely in the directory where you want to rename them to see what it would do, then if it will accomplish what you want you can uncomment the real move command and re-run it.

Updated per OP comments - this script considers a . having to do with a file extension up to 3 (less than 4) from the right hand side, change it to reflect your dataset.

Upvotes: 0

David C. Rankin
David C. Rankin

Reputation: 84599

A Bash Solution

This is one where a Bash solution may be useful as well. The following tests for an extension (max 4 chars + .) set with extsz in the script. If an extension is found, the script trims the whitespace from the filename and then moves the file from old to new name (actual move commented below). It relies on parameter expansion/substring replacement to manipulate the whitespace and filenames:

#!/bin/bash

declare -i extsz=-5     # extension w/i last 4 chars

## trim leading/trailing whitespace
function trimws {
    [ -z "$1" ] && return 1
    local strln="${#1}"
    [ "$strln" -lt 2 ] && return 1
    local trimstr=$1
    trimstr="${trimstr#"${trimstr%%[![:space:]]*}"}"  # remove leading whitespace characters
    trimstr="${trimstr%"${trimstr##*[![:space:]]}"}"  # remove trailing whitespace characters
    printf "%s" "$trimstr"
    return 0
}

## for each filename read from stdin
while read -r ffname || test -n "$ffname" ; do

    ## test for extension and set 'ext' if present
    for ((i=$extsz; i<0; i++)); do
        [ "${ffname:(i):1}" == '.' ] && { ext=${ffname:(i)}; break; }
    done

    ## if extension, move the file to name w/o trailing space w/orig extension
    if [ -n "$ext" ]; then

        fname="${ffname%.*}"          # separate filename from extension
        fnwosp="$(trimws "$fname")"   # trim whitespace from filename

        printf "   renaming :  '%s' -> '%s'\n" "$ffname" "${fnwosp}${ext}"
        #mv "$ffname" "${fnwosp}${ext}"  # commented for testing

    else
        ## if no extension, just trim whitespace and move
        printf "   renaming :  '%s' -> '%s'\n" "$ffname" "$(trimws "$ffname")"
        # mv "$ffname" "$(trimws "$ffname")"
    fi

    unset ext       # unset 'ext' for next iteration

done

exit 0

Input

$ cat dat/wfname.txt
QDN34 Unit5 mark-up - Judy .pdf
QDN34 Unit7 mark-up - Judy .pdf
file with two character ext .ai
file with dot. trailing space and no ext
file with no ext

Output

$ bash fixfilenames.sh <dat/wfname.txt
   renaming :  'QDN34 Unit5 mark-up - Judy .pdf' -> 'QDN34 Unit5 mark-up - Judy.pdf'
   renaming :  'QDN34 Unit7 mark-up - Judy .pdf' -> 'QDN34 Unit7 mark-up - Judy.pdf'
   renaming :  'file with two character ext .ai' -> 'file with two character ext.ai'
   renaming :  'file with dot. trailing space and no ext' -> 'file with dot. trailing space and no ext'
   renaming :  'file with no ext' -> 'file with no ext'

Note: when reading from stdin, the shell will strip trailing spaces for filenames without extensions.

Reading filenames as Arguments

To illustrate removing spaces from the end of filenames without extensions, it is necessary to quote and read the filenames as arguments. If that is what you need, here is a replacement. It probably makes more sense to read the filenames as arguments rather than in bulk from stdin anyway:

#!/bin/bash

declare -i extsz=-5     # extension w/i last 4 chars

## trim leading/trailing whitespace
function trimws {
    [ -z "$1" ] && return 1
    local strln="${#1}"
    [ "$strln" -lt 2 ] && return 1
    local trimstr=$1
    trimstr="${trimstr#"${trimstr%%[![:space:]]*}"}"  # remove leading whitespace characters
    trimstr="${trimstr%"${trimstr##*[![:space:]]}"}"  # remove trailing whitespace characters
    printf "%s" "$trimstr"
    return 0
}

## test at least 1 command line argument
[ $# -gt 0 ] || {
    printf "error: insufficient input.  usage: %s <filename>\n" "${0##*/}"
    exit 1
}

## for each of the filenames give as arguments
for ffname in "$@"; do

    ## test for extension and set 'ext' if present
    for ((i=$extsz; i<0; i++)); do
        [ "${ffname:(i):1}" == '.' ] && { ext=${ffname:(i)}; break; }
    done

    ## if extension, move the file to name w/o trailing space w/orig extension
    if [ -n "$ext" ]; then

        fname="${ffname%.*}"          # separate filename from extension
        fnwosp="$(trimws "$fname")"   # trim whitespace from filename

        printf "   renaming :  '%s' -> '%s'\n" "$ffname" "${fnwosp}${ext}"
        #mv "$ffname" "${fnwosp}${ext}"  # commented for testing

    else

        ## if no extension, just trim whitespace and move
        printf "   renaming :  '%s' -> '%s'\n" "$ffname" "$(trimws "$ffname")"
        # mv "$ffname" "$(trimws "$ffname")"

    fi

    unset ext

done

exit 0

Example

$ bash fixfilenames.sh 'testfile w end space '
   renaming :  'testfile w end space ' -> 'testfile w end space'

$ bash fixfilenames.sh 'file with two character ext .ai'
   renaming :  'file with two character ext .ai' -> 'file with two character ext.ai'

Upvotes: 1

Arjun Mathew Dan
Arjun Mathew Dan

Reputation: 5298

Try this:

find . -type f | while read i; do mv "$i" "$(sed 's/ \.\([a-z0-9]*\)$/\.\1/' <<< "$i")"; done;

For each file (recursively from current directory), remove the space before the file extension (using sed) and then rename the original file with new name (result of the sed command).

Upvotes: 0

Michael
Michael

Reputation: 1503

You may try the prename utility with a following perl regular expression:

user@host $ prename 's/\s(\..+$)/$1/g' *
  • \s - means space character
  • \. - means point
  • . - means one character
  • +$ - means repeating the previous symbol up to the end of the line
  • $1 - means replace with expression in ()

If you don't have this utility you can use the following bash script:

# you can replace the "*" in the line below
# with any necessary find or ls command to rename only necessary files
for old_name in *;
do
    new_name=$(echo "$old_name"|sed 's/ \(\.[^ \t]*$\)/\1/g')
    echo "\"$old_name\" --> \"$new_name\""
    mv "$old_name" "$new_name" 
done

Upvotes: 1

Related Questions