Git pre-commit hook searching for filenames with space

Viewed 499

my question is about git pre-commit hook.
i want to figure out, and disallow commits with filenames containing whitespaces.

#!/bin/bash
listchange=$(git diff --name-only) 

echo "Check filenames:"

wrongfilenames=$(
                for filename in $listchange 
                do
                    fname=$(basename $filename)
                    if ! [[ $fname =~ ^[0-9abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._]+$ ]]; then
                        echo $fname;
                    fi
                    if [[ $fname =~ [[:space:]]* ]]; then 
                        echo $fname;
                        echo "contains space"
                    fi
                done
            );
            
if [ -n "$wrongfilenames" ] ; then
    echo "The following filename contains invalid characters:"
    echo "$wrongfilenames"
    echo "Commit rejected, fix it and try again."
    exit 1
    else
    echo "....OK"

The First if, will check if the filename contains something else than 0-9, a-z, A-Z and . and _ .
there's no way to add a file containing "-".
For me it's not that clear, why whitespace passes this filter. Thats why i tried to create another if to check if filename contains a space. But this doesn't work as expected. \

thanks in advance

1 Answers

Remember that shells break up words based on $IFS, which defaults to being set to $' \t\n' (space, tab, and newline). We can see this with:

$ for word in $(echo "foo bar"); do echo "word=$word"; done
word=foo
word=bar

If we alter the IFS setting first, we get a more useful-to-us behavior:

$ IFS=$'\n'
$ for word in $(echo "foo bar"); do echo "word=$word"; done
word=foo bar

Of course, any files with newlines in their file names will cause problems here. Ideally, we'd use a separator or terminator character between or after each file name, that cannot appear in any file name. There are only two such characters: / and NUL ($'\0'). Slash, of course, appears in the path component separators, so if you're allowing paths rathe than mere file names (at the OS level), you must accept slashes.

Not all shells can deal with literal NUL bytes. (Not all shells even have the $'\n' syntax, for that matter.) So it might be better to write such a hook in, say, Python.

Most versions of bash can handle it, with some care, and you're already depending on bash [[ tests (and of course using /bin/bash explicitly). Or, you can just assume that while people might put spaces or even tabs into file names, they won't put newlines in them. So you can repair your script just by setting IFS up front.

(Also worth mentioning: it may be a good idea to use --name-status, or at least to filter away file-delete. Otherwise you'll prohibit someone from deleting a file with a space in its name. Whether that's desirable is up to you, of course.)

Related