如何在Bash中判断文件是否不存在?

3950
这个检查文件是否存在:
#!/bin/bash

FILE=$1     
if [ -f $FILE ]; then
   echo "File $FILE exists."
else
   echo "File $FILE does not exist."
fi

如何只检查文件是否不存在

231
我发现这个 Bash 条件语句列表 非常有用。 - Saulius Žemaitaitis
13
作为一个非常懒的人,我通常会使用以下愚蠢的解决方法:if [ -f $FILE ]; then; else; echo "File $FILE does not exist."; fi; 很幸运我找到了这个问题,学会了一种更合适的方法。 :) (备注:已经翻译成中文) - Alderath
8
严谨来说,你应该说“常规文件”,因为大多数UNIX/POSIX文档通称将所有类型的文件系统条目称为“文件”,例如符号链接是文件的一种类型,命名管道、常规文件、目录、块设备、字符设备、套接字等也都是文件。 - kevinarpe
13
如果你想测试 某个东西 是否存在,使用 -e-f无法识别目录、符号链接等。 - Benubird
18
为确保安全,请始终使用双引号正确处理带有空格的文件名,例如 FILE=$1 -> FILE="$1"if [ -f $FILE ]; -> if [ -f "$FILE" ]; - kevinarpe
显示剩余4条评论
20个回答

5332

test命令(此处写作[)具有“非”逻辑运算符!(感叹号):

if [ ! -f /tmp/foo.txt ]; then
    echo "File not found!"
fi

267
更简洁地说:[ ! -f /tmp/foo.txt ] && echo "文件未找到!" - DavidWinterbottom
43
我为“如果任意 2 个文件不存在”的正确语法有些苦恼。以下两种语法都可以使用: if [ ! \( -f "f1" -a -f "f2" \) ] ; then echo MISSING; fi if [ ! -f "f1" ] || [ ! -f "f2" ] ; then echo MISSING; fi - mivk
183
更加通俗易懂的翻译是:如果文件/tmp/foo.txt存在,那么什么也不做,否则输出"File not found!"。 - David W.
32
参数可以是以下任意一个: -e: 如果文件存在,则返回真值 -f: 如果文件存在且是普通文件,则返回真值 -r: 如果文件存在且可读,则返回真值 -w: 如果文件存在且可写,则返回真值 -x: 如果文件存在且可执行,则返回真值 -d: 如果存在且是目录,则返回真值 - SD.
15
在使用!-f&&以及使用-f||时存在一种不对称性。这与非/存在检查返回的退出代码有关。如果您需要使您的行始终以退出代码0干净地退出(有时您可能不想要这个约束),则这两种方法不能互换。或者,只需使用一个if语句,您就不再需要担心非/存在检查的退出代码。 - Asclepius
显示剩余8条评论

920

Bash文件测试

-b filename - 检查是否为块设备特殊文件
-c filename - 检查是否为字符设备特殊文件
-d directoryname - 检查目录是否存在
-e filename - 检查文件是否存在,不考虑类型(节点、目录、套接字等)
-f filename - 检查常规文件是否存在而非目录
-G filename - 检查文件是否存在并且属于有效组ID
-G filename set-group-id - 如果文件存在并设置了set-group-id,则为true
-k filename - 粘滞位
-L filename - 符号链接
-O filename - 如果文件存在且由有效用户ID拥有,则为真
-r filename - 检查文件是否可读
-S filename - 检查文件是否为套接字
-s filename - 检查文件大小是否为非零
-u filename - 检查文件设置了set-user-id位
-w filename - 检查文件是否可写
-x filename - 检查文件是否可执行

如何使用:

#!/bin/bash
file=./file
if [ -e "$file" ]; then
    echo "File exists"
else 
    echo "File does not exist"
fi 

使用 ! 运算符可以否定一个测试表达式

#!/bin/bash
file=./file
if [ ! -e "$file" ]; then
    echo "File does not exist"
else 
    echo "File exists"
fi 

2
如果您愿意,可以自由编辑我的帖子并将其添加到列表中。我猜你的意思是:-n String - 检查字符串的长度是否不为零。或者您是指 file1 -nt file2 - 检查文件1是否比文件2更新(您也可以使用 -ot 来检查是否更旧)。 - BlueCacti
4
关于-n: 一元运算符 -z 用于测试一个空字符串,而 -n 或无运算符则返回 True 如果一个字符串不是空的。~ http://www.ibm.com/developerworks/library/l-bash-test/index.html - BlueCacti
2
为什么没有人添加这样的函数:function exists() { if [ -e "$1" ]; then echo "$1 存在" else echo "$1 不存在" fi } - Mz A
1
因为我们需要让代码易读,没有人知道$1是什么。所以将$1赋值为“file”,然后使用该“file”变量执行其他操作,看起来比使用未知参数“$1”更易读。 - MaXi32
1
关于-G运算符,据我所知,这两者略有不同。根据chmod手册中的“SETUID和SETGID位”部分,在root用户的情况下,这两个值会有所不同,不是吗?我特别指的是“除非用户具有适当的权限”部分。无论如何,回答非常好。因为这个问题,我会收藏它。 - Nate T

339
使用 !test 中的表达式(对于该表达式,[ 是其别名)取反:
#!/bin/bash
FILE=$1

if [ ! -f "$FILE" ]
then
    echo "File $FILE does not exist"
fi

相关的 man 手册是 man test 或等效地,man [ -- 或使用内置 bash 命令的 help testhelp [

另外(不常用),您可以使用以下方式否定 test 的结果:

if ! [ -f "$FILE" ]
then
    echo "File $FILE does not exist"
fi

该语法在“man 1 bash”中的“Pipelines”和“Compound Commands”章节中有描述。


5
在 [tag:bash] 中,[ 是一个内置命令。因此,相关信息可以通过 help [ 获得……但是这表明 [test 内置命令的同义词,因此相关信息更适合通过 help test 来获取。另请参阅手册中的 Bash Conditional Expression 部分。 - gniourf_gniourf
1
@gniourf_gniourf:是的,但bash内置的[命令的行为与外部的[命令非常相似,因此无论是man test还是man [都可以让你了解它的工作原理。 - Keith Thompson
11
除了我系统上找到的外部命令 [ 之外,bash 内置命令 [ 还有更多的开关......一般来说,我认为阅读特定工具的文档比阅读另一个含糊相关的工具的文档更好。尽管我可能是错的 ;) - gniourf_gniourf

156
[[ -f $FILE ]] || printf '%s does not exist!\n' "$FILE"

此外,文件可能是一个损坏的符号链接,或者是一个非规则文件,例如套接字、设备或FIFO。例如,为了添加检查损坏的符号链接:

if [[ ! -f $FILE ]]; then
    if [[ -L $FILE ]]; then
        printf '%s is a broken symlink!\n' "$FILE"
    else
        printf '%s does not exist!\n' "$FILE"
    fi
fi

12
我可以问一下测试中为什么有两个“[”吗?(例如[[!-a $FILE]])。我在Solaris系统上尝试了所有提到的选项,只有这个有效,非常感激,但是为什么呢? - Dimitrios Mistriotis
36
双括号是一种“现代”扩展,例如它们不会对含有空格的文件名进行单词分割,并且仍然适用于空字符串:http://mywiki.wooledge.org/BashFAQ/031 - bw1024
9
根据http://tldp.org/LDP/abs/html/fto.html的内容,-a与-e的效果完全相同。它已经被“废弃”,不建议使用。无论如何,要注意检查损坏的符号链接。 - Luca Borrione
5
@dimitrismistriotis,两个“[”是一种非便携式的扩展,由zsh和bash以不同的方式实现;通常情况下,如果可能的话,请尽量避免使用它。 - Good Person
1
参见SO 321348,了解为什么在单括号条件表达式中无法否定-a。建议完全避免使用-a - Steve Powell
稍微修改了一下,以消除 -a 选项,但也因为 -a 等同于 -e,无论文件类型是否存在都会返回 true,在第二个示例中添加管道或套接字的检查时可能会遇到问题。 - ormaaj

117

值得一提的是,如果您需要执行单个命令,您可以缩写

if [ ! -f "$file" ]; then
    echo "$file"
fi
test -f "$file" || echo "$file"
或者
[ -f "$file" ] || echo "$file"

1
谢谢!需要一个不使用[[ ]]的替代方案。 - Jonathan
这被称为短路评估,是吗?它的工作原理是由于惰性评估,如果 A 为真,则在 A || BB 是无关紧要的。 - sb813322

82

我更喜欢使用以下一行代码,在POSIX兼容格式中:

$ [ -f "/$DIR/$FILE" ] || echo "$FILE NOT FOUND"

$ [ -f "/$DIR/$FILE" ] && echo "$FILE FOUND"

对于一些像在脚本中使用的命令:

$  [ -f "/$DIR/$FILE" ] || { echo "$FILE NOT FOUND" ; exit 1 ;}

一旦我开始这样做,我就很少再使用完全输入类型的语法了!!


1
首先,未加引号的变量引用容易出错。话虽如此,在任何bash manpage中都没有说[test内置默认会测试参数的文件存在性(而不是-e)?这不是含糊不清吗?据我所知(以及AIUI的“条件表达式”部分),你的方法只测试了参数不为空(或未定义),在这种情况下,这是一个重言(让$DIR =''$FILE ='',那么参数仍然是'//')。 - PointedEars
1
证明:ls /foo,结果为 ls: 无法访问 /foo: 没有那个文件或目录[ /foo ] && echo 42,结果为 42。GNU bash,版本 4.2.37(1)-release (i486-pc-linux-gnu)。 - PointedEars
1
@PointedEars:在我写这篇答案的时候,我没有指定“-f”选项。如果你不确定它是否是一个常规文件,显然你可以始终使用“-e”。此外,在我所有的脚本中,我都引用了这些结构,我可能只是没有充分证明就提交了这个。 - J. M. Becker
确认。但是你可能知道一个一行代码无法解决if-else问题:[ $condition ] && if_true || if_false容易出错。无论如何,我发现[ ! -f "$file" ] && if_not_exists[ -f "$file" ] || if_not_exists更易于阅读和理解。 - PointedEars

66

要测试文件是否存在,参数可以是以下任意一个:

-e: Returns true if file exists (regular file, directory, or symlink)
-f: Returns true if file exists and is a regular file
-d: Returns true if file exists and is a directory
-h: Returns true if file exists and is a symlink

以下所有测试都适用于普通文件、目录和符号链接:

-r: Returns true if file exists and is readable
-w: Returns true if file exists and is writable
-x: Returns true if file exists and is executable
-s: Returns true if file exists and has a size > 0

示例脚本:

#!/bin/bash
FILE=$1

if [ -f "$FILE" ]; then
   echo "File $FILE exists"
else
   echo "File $FILE does not exist"
fi

53
你可以这样做:
[[ ! -f "$FILE" ]] && echo "File doesn't exist"

或者

if [[ ! -f "$FILE" ]]; then
    echo "File doesn't exist"
fi

如果你想检查文件和文件夹,可以使用-e选项而不是-f-e对于普通文件、目录、套接字、字符特殊文件、块特殊文件等都返回true。

3
这不是Bash特有的。语法来自Korn shell,也适用于zsh和bash。但在这种情况下,它与标准 [ 工具相比并没有太多优势。 - Stephane Chazelas
1
“正规”文件(由“-f”检查)和“目录”只是许多不同类型的“文件”中的两种。还有套接字、符号链接、设备、FIFO、门等等…… [ -e将测试文件是否存在(包括常规、FIFO、目录等任何类型),在符号链接解析之后。 - Stephane Chazelas
2
@StephaneChazelas:我没有看到任何ksh或zsh标签。我们正在谈论的是bash或者在大多数Unix系统上标准化的东西。因此,在这个上下文中,它是特定于bash的。OP不需要知道所有其他shell的存在:D - Jahid
1
这是你回答中关于“Bash特定部分”的注释。 - Stephane Chazelas
1
@StephaneChazelas:是的,在上下文中(bash和标准sh),它是特定于bash的。我不明白你第二条评论的意义,它完全脱离了上下文。OP不需要-e,他需要-f - Jahid
显示剩余2条评论

43

你需要注意对未加引号的变量运行test命令,因为它可能会产生意外结果:

$ [ -f ]
$ echo $?
0
$ [ -f "" ]
$ echo $?
1

通常建议将被测试的变量用双引号括起来:

#!/bin/sh
FILE=$1

if [ ! -f "$FILE" ]
then
   echo "File $FILE does not exist."
fi

8
建议每个变量都用双引号括起来,除非你确切知道有一些罕见情况下不需要使用引号,或者更罕见的情况是使用引号会导致错误。(注意,这并不是其中之一。) - Uwe
您能详细解释一下为什么不能使用双引号吗?否则我就看不出评论的用处了。 - artdanil
4
我的意思是:这不是那些不必要或有害的罕见情况之一。Shell程序员应该习惯于将(几乎)每个变量用双引号括起来;这条规则不仅限于“[ ... ]”。 - Uwe
请参见https://dev59.com/NWkw5IYBdhLWcg3wMHum。 - tripleee

32

[ -f "$file" ]

[命令对存储在$file中的路径执行stat()(而不是lstat())系统调用,并且如果该系统调用成功并且stat()返回的文件类型为“regular”,则返回true

因此,如果[ -f "$file" ]返回true,则可以确定该文件确实存在且是普通文件或最终解析为普通文件的符号链接(或者至少在stat()的时间上是这样)。

但是,如果它返回false(或者[ ! -f "$file" ]! [ -f "$file" ]返回true),则有许多不同的可能性:

  • 文件不存在
  • 文件存在,但不是一个普通的文件(可能是设备、FIFO、目录、套接字等)
  • 文件存在,但你没有搜索父目录的权限
  • 文件存在,但访问路径太长
  • 该文件是一个符号链接到一个普通文件,但你没有搜索解析符号链接涉及的某些目录的权限。
  • ... stat()系统调用失败的任何其他原因。

简而言之:

if [ -f "$file" ]; then
  printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
  printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
  printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
  printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi

为了确定文件不存在,我们需要使用stat()系统调用返回错误代码ENOENT(在路径中有一个组件不是目录时,ENOTDIR告诉我们也可以通过该路径判断文件不存在)才能确定。不幸的是,[命令并不能告诉我们这一点。无论错误代码是ENOENT、EACCESS(权限被拒绝)、ENAMETOOLONG还是其他任何东西,它都会返回false。 [ -e "$file" ]测试也可以通过ls -Ld -- "$file" > /dev/null来完成。在这种情况下,ls将告诉您为什么stat()失败,尽管这些信息不能轻松地用于编程:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed

至少ls告诉我它失败不是因为文件不存在,而是因为它无法确定文件是否存在。 [命令只是忽略了这个问题。

使用zsh shell,在失败的[命令之后,您可以使用$ERRNO特殊变量查询错误代码,并使用zsh/system模块中的$errnos特殊数组解码该数字:

zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
  err=$ERRNO
  case $errnos[err] in
    ("") echo exists, not a regular file;;
    (ENOENT|ENOTDIR)
       if [ -L "$file" ]; then
         echo broken link
       else
         echo does not exist
       fi;;
    (*) syserror -p "can't tell: " "$err"
  esac
fi

(请注意,当使用最新版本的gcc构建时,$errnos支持与某些版本的zsh存在故障。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接