Check if passed argument is file or directory in Bash

BashShell

Bash Problem Overview


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.

Bash Solutions


Solution 1 - Bash

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.

Solution 2 - Bash

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.

Solution 3 - Bash

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}

Solution 4 - Bash

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

Solution 5 - Bash

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
}

Solution 6 - Bash

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

Solution 7 - Bash

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

Solution 8 - Bash

#!/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 

Solution 9 - Bash

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)

Solution 10 - Bash

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndyView Question on Stackoverflow
Solution 1 - BashJohn KugelmanView Answer on Stackoverflow
Solution 2 - BashJonathan LefflerView Answer on Stackoverflow
Solution 3 - BashMike QView Answer on Stackoverflow
Solution 4 - BashKamranView Answer on Stackoverflow
Solution 5 - BashrofrolView Answer on Stackoverflow
Solution 6 - BashJaison JohnView Answer on Stackoverflow
Solution 7 - BashMarinos AnView Answer on Stackoverflow
Solution 8 - BashmujView Answer on Stackoverflow
Solution 9 - BashnoelmcloughlinView Answer on Stackoverflow
Solution 10 - Bashuser11791326View Answer on Stackoverflow