226

I'm trying to write an extremely simple script in Ubuntu which would allow me to pass it either a filename or a directory, and be able to do something specific when it's a file, and something else when it's a directory. The problem I'm having is when the directory name, or probably files too, has spaces or other escapable characters are in the name.

Here's my basic code down below, and a couple tests.

#!/bin/bash

PASSED=$1

if [ -d "${PASSED}" ] ; then
    echo "$PASSED is a directory";
else
    if [ -f "${PASSED}" ]; then
        echo "${PASSED} is a file";
    else
        echo "${PASSED} is not valid";
        exit 1
    fi
fi

And here's the output:

andy@server~ $ ./scripts/testmove.sh /home/andy/
/home/andy/ is a directory

andy@server~ $ ./scripts/testmove.sh /home/andy/blah.txt
/home/andy/blah.txt is a file

andy@server~ $ ./scripts/testmove.sh /home/andy/blah\ with\ a\ space.txt
/home/andy/blah with a space.txt is not valid

andy@server~ $ ./scripts/testmove.sh /home/andy\ with\ a\ space/
/home/andy with a space/ is not valid

All of those paths are valid, and exist.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Andy
  • 2,263
  • 2
  • 14
  • 4

10 Answers10

298

That should work. I am not sure why it's failing. You're quoting your variables properly. What happens if you use this script with double [[ ]]?

if [[ -d $PASSED ]]; then
    echo "$PASSED is a directory"
elif [[ -f $PASSED ]]; then
    echo "$PASSED is a file"
else
    echo "$PASSED is not valid"
    exit 1
fi

Double square brackets is a bash extension to [ ]. It doesn't require variables to be quoted, not even if they contain spaces.

Also worth trying: -e to test if a path exists without testing what type of file it is.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
19

At least write the code without the bushy tree:

#!/bin/bash

PASSED=$1

if   [ -d "${PASSED}" ]
then echo "${PASSED} is a directory";
elif [ -f "${PASSED}" ]
then echo "${PASSED} is a file";
else echo "${PASSED} is not valid";
     exit 1
fi

When I put that into a file "xx.sh" and create a file "xx sh", and run it, I get:

$ cp /dev/null "xx sh"
$ for file in . xx*; do sh "$file"; done
. is a directory
xx sh is a file
xx.sh is a file
$

Given that you are having problems, you should debug the script by adding:

ls -ld "${PASSED}"

This will show you what ls thinks about the names you pass the script.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
6

Using -f and -d switches on /bin/test:

F_NAME="${1}"

if test -f "${F_NAME}"
then                                   
   echo "${F_NAME} is a file"
elif test -d "${F_NAME}"
then
   echo "${F_NAME} is a directory"
else                                   
   echo "${F_NAME} is not valid"
fi
Kamran
  • 843
  • 1
  • 8
  • 19
  • 2
    Generally speaking, you shouldn't bother to add a new answer to an old question when there are equivalent answers already available. If you have some startling new information to add, but all means give an answer. But what you've said has already been said. (`test` is normally a shell built-in, though there usually is also an executable such as `/bin/test`, and also `/bin/[`.) – Jonathan Leffler Mar 22 '19 at 15:20
5

Using the "file" command may be useful for this:

#!/bin/bash
check_file(){

if [ -z "${1}" ] ;then
 echo "Please input something"
 return;
fi

f="${1}"
result="$(file $f)"
if [[ $result == *"cannot open"* ]] ;then
        echo "NO FILE FOUND ($result) ";
elif [[ $result == *"directory"* ]] ;then
        echo "DIRECTORY FOUND ($result) ";
else
        echo "FILE FOUND ($result) ";
fi

}

check_file "${1}"

Output examples :

$ ./f.bash login
DIRECTORY FOUND (login: directory) 
$ ./f.bash ldasdas
NO FILE FOUND (ldasdas: cannot open `ldasdas' (No such file or  directory)) 
$ ./f.bash evil.php 
FILE FOUND (evil.php: PHP script, ASCII text) 

FYI: the answers above work but you can use -s to help in weird situations by checking for a valid file first:

#!/bin/bash

check_file(){
    local file="${1}"
    [[ -s "${file}" ]] || { echo "is not valid"; return; } 
    [[ -d "${file}" ]] && { echo "is a directory"; return; }
    [[ -f "${file}" ]] && { echo "is a file"; return; }
}

check_file ${1}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Mike Q
  • 6,716
  • 5
  • 55
  • 62
  • So, empty files aren't valid (because `-s` checks for a non-empty file, one with a non-zero size)? And you won't print any diagnosis for a block special, character special, FIFO, etc? Symlinks probably resolve to what's at the far end of the link; broken symlinks are more problematic. – Jonathan Leffler Mar 22 '19 at 15:14
  • What do you suggest as an edit, I'm not following your commentary – Mike Q Mar 23 '19 at 00:31
  • Use the `--brief` flag of `file`. It merely outputs `directory` when it is. – Berkant İpek Jul 13 '19 at 05:38
4

Using stat

function delete_dir () {
  type="$(stat --printf=%F "$1")"
  if [ $? -ne 0 ]; then
    echo "$1 directory does not exist. Nothing to delete."
  elif [ "$type" == "regular file" ]; then
    echo "$1 is a file, not a directory."
    exit 1
  elif [ "$type" == "directory" ]; then
    echo "Deleting $1 directory."
    rm -r "$1"
  fi
}

function delete_file () {
  type="$(stat --printf=%F "$1")"
  if [ $? -ne 0 ]; then
    echo "$1 file does not exist. Nothing to delete."
  elif [ "$type" == "directory" ]; then
    echo "$1 is a regular file, not a directory."
    exit 1
  elif [ "$type" == "regular file" ]; then
    echo "Deleting $1 regular file."
    rm "$1"
  fi
}
rofrol
  • 14,438
  • 7
  • 79
  • 77
0

A more elegant solution

echo "Enter the file name"
read x
if [ -f $x ]
then
    echo "This is a regular file"
else
    echo "This is a directory"
fi
  • 3
    Prompting for inputs instead of using command line arguments is usually not "more elegant". You should quote the variable in the test: `if [ -f "$x" ]`. – Jonathan Leffler Mar 22 '19 at 15:06
  • If the test fails, it's not necessarily a directory, could be non existent path/file – Sabrina Jun 21 '23 at 15:19
-1

Answer based on the title:

Check if passed argument is file or directory in Bash

This works also if the provided argument has a trailing slash .e.g. dirname/

die() { echo $* 1>&2; exit 1; }
# This is to remove the the slash at the end: dirName/ -> dirName
fileOrDir=$(basename "$1")
( [ -d "$fileOrDir" ] || [ -f "$fileOrDir" ] ) && die "file or directory  $fileOrDir already exists"

Testing:

mkdir mydir
touch myfile

command dirName
# file or directory  mydir already exists
command dirName/
# file or directory  mydir already exists
command filename
# file or directory  myfile already exists
Marinos An
  • 9,481
  • 6
  • 63
  • 96
-2
#!/bin/bash                                                                                               
echo "Please Enter a file name :"                                                                          
read filename                                                                                             
if test -f $filename                                                                                      
then                                                                                                      
        echo "this is a file"                                                                             
else                                                                                                      
        echo "this is not a file"                                                                         
fi 
Goralight
  • 2,067
  • 6
  • 25
  • 40
muj
  • 9
  • It's best to echo the file name in the output, as the code in the question does. It isn't clear that prompting for a file name is an improvement. The code doesn't distinguish between files, directories and 'other'. It's OK to add a new answer to an old question if there is no answer, or you have new information to impart. That isn't the case here. – Jonathan Leffler Mar 22 '19 at 15:15
-2

One liner

touch bob; test -d bob && echo 'dir' || (test -f bob && echo 'file')

result is true (0)(dir) or true (0)(file) or false (1)(neither)

noelmcloughlin
  • 1,723
  • 1
  • 12
  • 10
-2

This should work:

#!/bin/bash

echo "Enter your Path:"
read a

if [[ -d $a ]]; then 
    echo "$a is a Dir" 
elif [[ -f $a ]]; then 
    echo "$a is the File" 
else 
    echo "Invalid path" 
fi
Asclepius
  • 57,944
  • 17
  • 167
  • 143