13

I'm doing the following:

if [ -f $FILE ] ; then
    echo "File exists"
fi

But I want the -f to be case-insensitive. That is, if FILE is /etc/somefile, I want -f to recognize /Etc/SomeFile.

I can partially work around it with glob:

shopt -s nocaseglob
TARG='/etc/somefile'

MATCH=$TARG*    #assume it returns only one match
 
if [[ -f $MATCH ]] ; then
    echo "File exists" 
fi

but the case-insensitive globbing works only on the filename portion, not the full path. So it will not work if TARG is /Etc/somefile.

Is there any way to do this?

John Smith
  • 7,243
  • 6
  • 49
  • 61
Paul Richter
  • 6,154
  • 2
  • 20
  • 22
  • take a look to http://stackoverflow.com/a/12953235/848072 for a thorough analysis of insensitive **find** – albfan Jan 10 '13 at 10:08

7 Answers7

15

The problem is that your filesystem is case-sensitive. The filesystem provides only two relevant ways to get a file: either you specify an exact, case-sensitive filename and check for its existence that way, or you read all the files in a directory and then check if each one matches a pattern.

In other words, it is very inefficient to check if a case-insensitive version of a file exists on a case-sensitive filesystem. The shell might do it for you, but internally it's reading all the directory's contents and checking each one against a pattern.

Given all that, this works:

if [[ -n $(find /etc -maxdepth 1 -iname passwd) ]]; then
  echo "Found";
fi

BUTunless you want to search everything from '/' on down, you must check component of the path individually. There is no way around this; you can't magically check a whole path for case-insensitive matches on a case-sensitive filesystem!

John Smith
  • 7,243
  • 6
  • 49
  • 61
Borealid
  • 95,191
  • 9
  • 106
  • 122
  • 1
    `find / -iwholename` would probably also work, but would be grossly inefficient. – Philipp Jul 09 '10 at 09:04
  • @Philipp: yeah, as I said, "unless you want to search everything from '/' on down"... And if you have any circular symlinks, pray... – Borealid Jul 09 '10 at 09:14
  • someone should probably tell this to debian (pkg/debian: ok; pkg/Debian: good to go!; pkg/DeBiAn: why not?!) – Nate T Sep 17 '21 at 22:40
  • On a ***wholename***, `find` could be lot too slow! Have a look at [my `ipath` function](https://stackoverflow.com/a/74123812/1765658) – F. Hauri - Give Up GitHub Oct 19 '22 at 10:33
2

not knowing howto only using shell evaluations. but grep can be case-insensitive, therefore a script that invokes grep , find and wc may meet your demand.

Jokester
  • 5,501
  • 3
  • 31
  • 39
2

you can use the nocasematch option

shopt -s nocasematch
for file in *
do
  case "$file" in
   "/etc/passWd" ) echo $file;;
  esac
done 
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
2

This is very hard to do, in general, if the file system is case-sensitive. Basically, you have iterate over each of the ancestor directories separately. Here is a starting point in Python:

import os

def exists_nocase(path):
    if os.path.exists(path):
        return True
    path = os.path.normpath(os.path.realpath(os.path.abspath(unicode(path)))).upper()
    parts = path.split(os.sep)
    path = unicode(os.path.join(unicode(os.path.splitdrive(path)[0]), os.sep))
    for name in parts:
        if not name:
            continue
        # this is a naive and insane way to do case-insensitive string comparisons:
        entries = dict((entry.upper(), entry) for entry in os.listdir(path))
        if name in entries:
            path = os.path.join(path, entries[name])
        else:
            return False
    return True

print exists_nocase("/ETC/ANYTHING")
print exists_nocase("/ETC/PASSWD")
Nate T
  • 727
  • 5
  • 21
Philipp
  • 48,066
  • 12
  • 84
  • 109
1

As this question is tagged

Introduction

I was searching for window's registry file after mounted NTFS partition in a temporary mount point mnt=$(mktemp -d)

Then, if the command:

regfile=$(find $mnt -iwholename "$mnt/window*/system32/config/software")

do the job, as this command is based on $mnt, find will stupidely scan the whole filesystem and take a lot of time.

My pure bash function

For this, I wrote this ipath function:

ipath() {
    if [[ $1 == -v ]]; then local -n _res=$2; shift 2; else local _res; fi
    local _path _i _chr
    _res=
    for ((_i=0;_i<${#1};_i++));do
        _chr=${1:_i:1} _chr=${_chr,}
        [[ $_chr != ${_chr^} ]] && _path+="[$_chr${_chr^}]" ||
                _path+=$_chr
    done
    _path=($_path)
    [[ -e ${_path[0]} ]] || return 1
    if [[ ${_res@A} == _res=* ]] ;then
        printf "%s\n" "${_path[@]}"
    else
        _res=("${_path[@]}")
    fi
}

Then for testing:

$ ipath "$mnt/window*/system32/config/software"
/tmp/tmp.Q2czvPDs5m/Windows/System32/config/SOFTWARE

$ ipath ~/desktop
/home/user/Desktop

for use as a variable:

$ ipath -v regfile "$mnt/window*/system32/config/software"
$ echo $regfile 
/tmp/tmp.Q2czvPDs5m/Windows/System32/config/SOFTWARE

$ ipath -v DeskDir ~/desktop
echo $DeskDir
/home/user/Desktop

Another test with multiple answer and spaced path:

$ ipath $mnt/program\*/int\*er
/tmp/tmp.jVs5mPGbJm/Program Files/Internet Explorer
/tmp/tmp.jVs5mPGbJm/Program Files (x86)/Internet Explorer

$ ipath -v iedirs "$mnt/program*/int*er"
$ declare -p iedirs
declare -a iedirs=([0]="/tmp/tmp.jVs5mPGbJm/Program Files/Internet Explorer" [1]="/tmp/tmp.jVs5mPGbJm/Program Files (x86)/Internet Explorer")

Note, tested with unicode accented characters: résumé.pdf become [rR][éÉ][sS][uU][mM][éÉ].[pP][dD][fF].

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
0
cd /etc
# using FreeBSD find
find -x -L "$(pwd)" -maxdepth 1 -type f -iregex "/EtC/[^\/]*" -iname paSSwd 
carlo
  • 1
0

And here's a starting point using Bash:

#  fci -- check if file_case_insensitive exists 
# (on a case sensitive file system; complete file paths only!)

function fci() {   

declare IFS checkdirs countslashes dirpath dirs dirstmp filepath fulldirpaths i name ndirs result resulttmp

[[ -f "$1" ]] && { return 0; }
[[ "${1:0:1}" != '/' ]] && { echo "No absolute file path given: ${1}" 2>&1; return 1; }
[[ "$1" == '/' ]] && { return 1; }

filepath="$1"
filepath="${filepath%"${filepath##*[!/]}"}"  # remove trailing slashes, if any
dirpath="${filepath%/*}"
name="${filepath##*/}"

IFS='/'
dirs=( ${dirpath} )

if [[ ${#dirs[@]} -eq 0 ]]; then
   fulldirpaths=( '/' )
   ndirs=1
else
   IFS=""
   dirs=( ${dirs[@]} )
   ndirs=${#dirs[@]}

   for ((i=0; i < ${ndirs}; i++)); do

      if [[ $i -eq 0 ]]; then
         checkdirs=( '/' )
      else
         checkdirs=( "${dirstmp[@]}" )
      fi

      IFS=$'\777'
      dirstmp=( $( find -x -L "${checkdirs[@]}" -mindepth 1 -maxdepth 1 -type d -iname "${dirs[i]}" -print0 2>/dev/null | tr '\0' '\777' ) )

      IFS=""
      fulldirpaths=( ${fulldirpaths[@]} ${dirstmp[@]} )

   done

fi

printf "fulldirpaths: %s\n" "${fulldirpaths[@]}" | nl

for ((i=0; i < ${#fulldirpaths[@]}; i++)); do
   countslashes="${fulldirpaths[i]//[^\/]/}"
   [[ ${#countslashes} -ne ${ndirs} ]] && continue
   IFS=$'\777'
   resulttmp=( $( find -x -L "${fulldirpaths[i]}" -mindepth 1 -maxdepth 1 -type f -iname "${name}" -print0 2>/dev/null | tr '\0' '\777' ) )
   IFS=""
   result=( ${result[@]} ${resulttmp[@]} )
done

IFS=""
result=( ${result[@]} )

printf "result: %s\n" "${result[@]}" | nl

if [[ ${#result[@]} -eq 0 ]]; then
   return 1
else
   return 0
fi
}


FILE='/eTC/paSSwD'

if fci "${FILE}" ; then
   echo "File (case insensitive) exists: ${FILE}" 
else
   echo "File (case insensitive) does NOT exist: ${FILE}" 
fi
juan
  • 1