Bash – How to find all paths not compatible with Windows

bashext2fsdfilenamesshellwindows

On most Linux filesystems, NUL (\0) is the only invalid character in paths (and / is reserved as the path separator). Windows has a complicated set of rules for valid paths. Rather than fixing paths automagically (dangerous, could result in a file overwriting another), how can I find all paths within a directory not compatible with Windows?

The original issue was that my Google Drive folder was on a drive mounted using ext2fs, but the official Gdrive client told me thousands of files could not be synced. I could find no error messages, and when I asked it to show me the files it would simply hang indefinitely. Restarting the client or OS did not help, but I had a hunch fixing any non-Windows-compatible paths would unstick Gdrive. It seems to have worked…

Best Answer

Paths with reserved names/characters:

LC_ALL=C find . -name '*[[:cntrl:]<>:"\\|?*]*' \
             -o -iname 'CON' \
             -o -iname 'PRN' \
             -o -iname 'AUX' \
             -o -iname 'NUL' \
             -o -iname 'COM[1-9]' \
             -o -iname 'LPT[1-9]' \
             -o -name '* ' \
             -o -name '?*.'

Test:

$ cd -- "$(mktemp --directory)"
$ touch foo \\ LPT9 'space ' 'dot.'
$ LC_ALL=C find . -name '*[[:cntrl:]<>:"\\|?*]*' -o -iname 'CON' -o -iname 'PRN' -o -iname 'AUX' -o -iname 'NUL' -o -iname 'COM[1-9]' -o -iname 'LPT[1-9]' -o -name '* ' -o -name '?*.'
./dot.
./space 
./LPT9
./\

Paths which are identical when ignoring case (does not work with paths containing newlines):

find . | sort | LC_ALL=C tr '[:upper:]' '[:lower:]' | uniq -c | grep -v '^      1 ' | cut -c '9-'

Test:

$ cd -- "$(mktemp --directory)"
$ touch foo bar BAR
$ find . | sort | LC_ALL=C tr '[:upper:]' '[:lower:]' | LC_ALL=C uniq -c | grep -v '^      1 ' | cut -c '9-'
./bar

Paths longer than MAX_PATH (260 characters):

find "$PWD" | while IFS= read -r path
do
    if [ "${#path}" -gt 260 ]
    then
        printf '%s\n' "$path"
    fi
done

Test:

$ cd -- "$(mktemp --directory)"
$ touch foo 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
$ find "$PWD" | while IFS= read -r path
> do
>     if [ "${#path}" -gt 260 ]
>     then
>         printf '%s\n' "$path"
>     fi
> done
/tmp/tmp.HEANyAI8Hy/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345