Search code examples
bashsedcut

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

Solution

  • 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'