题 如何判断Bash中是否存在常规文件?


我使用以下脚本来查看文件是否存在:

#!/bin/bash

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

如果我只想检查文件是否正确,那么使用的语法是什么  存在?

#!/bin/bash

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

2518
2018-03-12 14:48


起源


我发现了这个 bash条件语句列表 很有用。 - Saulius Žemaitaitis
作为一个非常懒惰的人,我通常会使用以下愚蠢的变通方法构造: if [ -f $FILE ]; then; else; echo "File $FILE does not exist."; fi; 可能很好,我发现这个问题,并学会以更恰当的方式做到这一点。 :) - Alderath
要成为pendantic,你应该说“常规文件”,因为大多数UNIX / POSIX文档一般都是指所有类型的文件系统条目,只是简单的“文件”,例如,符号链接是一种文件类型,就像命名管道一样,常规文件,目录,块特殊,字符特殊,套接字等 - kevinarpe
@kevinarpe如果你想测试是否 某物 存在,使用 -e。 -f不会选择目录,符号链接等。 - Benubird
为安全起见,请始终使用双引号来正确处理带有空格的文件名,例如: FILE=$1  - > FILE="$1" 和 if [ -f $FILE ];  - > if [ -f "$FILE" ]; - kevinarpe


答案:


测试 命令([ 这里有一个“非”逻辑运算符,它是感叹号(类似于许多其他语言)。尝试这个:

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

3529
2018-03-12 14:50



我努力寻找“如果2个文件中不存在任何文件”的正确语法。以下两项工作: if [ ! \( -f "f1" -a -f "f2" \) ] ; then echo MISSING; fi  if [ ! -f "f1" ] || [ ! -f "f2" ] ; then echo MISSING; fi - mivk
@DavidWinterbottom更加多汁: [ -f /tmp/foo.txt ] || echo "File not found!" - David W.
参数可以是以下任何一种: -e: Returns true value, if file exists  -f: Return true value, if file exists and regular file  -r: Return true value, if file exists and is readable  -w: Return true value, if file exists and is writable  -x: Return true value, if file exists and is executable  -d: Return true value, if exists and is a directory - SD.
如果要在if操作中使用2个条件,请使用-a运算符,这意味着AND。我认为如果您使用[[]]运算符,这也是可能的 - GroundZero
使用中存在不对称性 ! -f 同 && 与使用 -f 同 ||。这与非/存在检查返回的退出代码有关。如果您需要使用退出代码0来彻底退出行(有时您不需要此约束),则这两种方法不可互换。或者,只需使用 if 声明,您不再需要担心您的非/存在检查的退出代码。 - A-B-B


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所有,则为True
-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 

429
2018-01-16 14:25



@ 0x90如果需要,您可以自由编辑我的帖子并将其添加到列表中。我想你的意思是: -n String  - 检查字符串的长度是否不为零。或者你的意思是 file1 -nt file2  - 检查file1是否比文件2更新(你也可以使用-ot作为旧版本) - GroundZero
关于-n: The unary operator -z tests for a null string, while -n or no operator at all returns True if a string is not empty. 〜 ibm.com/developerworks/library/l-bash-test/index.html - GroundZero


你可以用“!”来否定表达式:

#!/bin/bash
FILE=$1

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

相关的手册页是 man test 或者,等效地, man [  - 要么 help test 要么 help [ 用于内置bash命令。


250
2018-03-12 14:50



在 庆典, [ 是一个内置的。因此,相关信息是通过 help [......但这表明了这一点 [ 是...的同义词 test 内置,因此相关信息是通过 help test。另见 手册中的Bash条件表达式部分。 - gniourf_gniourf
@gniourf_gniourf:是的,但内置了bash [ 命令行为与外部非常相似 [ 命令,所以 man test 要么 man [ 会让你很好地了解它的工作原理。 - Keith Thompson
@KeithThompson除了那个 庆典 内置 [ 有比外部命令更多的开关 [ 在我的系统上找到...一般来说,我认为最好阅读特定于给定工具的文档,而不是特定于另一个模糊相关文档的文档。不过我可能错了 ;) - gniourf_gniourf


[[ -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

110
2018-03-12 14:52



我可以问为什么两个“[”s在测试中? (例如[[!-a $ FILE]])。我尝试了在solaris盒子上提到的所有选项,只有那个工作,非常感激,但为什么呢? - Dimitrios Mistriotis
双括号是“现代”的延伸;例如,他们不会进行单词拆分(例如对于带有空格的文件名),并且仍然适用于空字符串: mywiki.wooledge.org/BashFAQ/031 - bw1024
根据 tldp.org/LDP/abs/html/fto.html -a与-e的效果相同。它已被“弃用”,并且不鼓励使用它。无论如何+1提及检查破坏的符号链接 - Luca Borrione
@dimitrismistriotis two“[”是由zsh&bash实现的(不同的)非便携式扩展;一般来说,如果可能的话你应该避免它。 - Good Person
我投了这个,因为它使用了[[。它是一种广泛使用的扩展。如果您知道您正在使用bash,那么没有理由不使用它。它比[更不容易出错]。 - Michael Potter


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

if [ ! -f "$file" ]; then
    echo "$file"
fi

test -f "$file" || echo "$file"

要么

[ -f "$file" ] || echo "$file"

80
2017-08-11 09:15



谢谢!需要一个不使用[[]]的替代方案 - Jonathan


我更喜欢做以下单行,在 POSIX shell兼容格式:

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

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

对于一些命令,就像我在脚本中所做的那样:

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

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


57
2017-10-18 16:09



首先,不带引号的变量引用容易出错。那说,它在哪里说 任何 bash manpage那个 [ 要么 test 内置将测试 文件存在 默认情况下的参数(相对于 -e)?这不是模棱两可的吗? AFAIK(和AIUI的“条件表达式”部分)唯一用您的方法测试的是参数不是空的(或未定义的),在这种情况下,是一个重言式(让 $DIR = '' 和 $FILE = ''那么争论仍然存在 '//')。 - PointedEars
证明: ls /foo结果 ls: cannot access /foo: No such file or directory。 [ /foo ] && echo 42结果 42。 GNU bash,版本4.2.37(1) - release(i486-pc-linux-gnu)。 - PointedEars
@PointedEars:我没有指定 -f 选项,目前我写了这个答案。显然你可以随时使用 -e,如果你不确定它将是一个普通文件。另外在我的所有脚本中,我引用了这些结构,我必须在没有充分校对的情况下提交它。 - TechZilla
ACK。但是你可能知道单线程无法解决if-else问题: [ $condition ] && if_true || if_false 很容易出错。无论如何,我发现 [ ! -f "$file" ] && if_not_exists 比阅读和理解更容易 [ -f "$file" ] || if_not_exists。 - PointedEars


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

-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

36
2017-12-19 12:47





你应该小心跑步 test 对于未加引号的变量,因为它可能会产生意外的结果:

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

建议通常是让测试变量用双引号括起来:

#!/bin/sh
FILE=$1

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

28
2018-01-24 19:43



建议是有 一切 变量被双引号括起来,除非你确切地知道你有一个罕见的情况是不必要的,或者是一个甚至更罕见的情况,它是有害的。 (不,这不是其中之一。) - Uwe
您是否愿意详细说明为什么不使用双引号?否则我没有看到评论中的用处。 - artdanil
我的意思是:这不是罕见的不必要或有害的案例之一。 shell程序员应该习惯用双引号括起(几乎)每个变量;这条规则不仅限于此 [ ... ]。 - Uwe
也可以看看 stackoverflow.com/questions/10067266/... - tripleee


有三种不同的方法可以做到这一点:

  1. 用bash否定退出状态(没有其他答案说过这个):

    if ! [ -e "$file" ]; then
        echo "file does not exist"
    fi
    

    要么:

    ! [ -e "$file" ] && echo "file does not exist"
    
  2. 否定测试命令中的测试 [ (这是大多数答案之前提出的方式):

    if [ ! -e "$file" ]; then
        echo "file does not exist"
    fi
    

    要么:

    [ ! -e "$file" ] && echo "file does not exist"
    
  3. 根据测试结果判定为否定(|| 代替 &&):

    只要:

    [ -e "$file" ] || echo "file does not exist"
    

    这看起来很愚蠢(IMO),不要使用它,除非你的代码必须可移植到Bourne shell(比如 /bin/sh Solaris 10或更早版本)缺少管道否定运算符(!):

    if [ -e "$file" ]; then
        :
    else
        echo "file does not exist"
    fi
    

18
2017-08-05 18:10



任何便携性之间的区别 ! [ 和 [ !? - Frozen Flame
该 ! [  是POSIX for shell管道2.9.2(任何命令)  Otherwise, the exit status shall be the logical NOT of the exit status of the last command 和 [ !   是POSIX进行测试  ! expression True if expression is false. False if expression is true. 所以,两者都是POSIX,根据我的经验,两者都得到了广泛的支持。
@FrozenFlame,Bourne shell没有了 ! 由Korn shell引入的关键字。除了Solaris 10及更早版本之外,这些天你不太可能遇到Bourne shell。 - Stephane Chazelas


你可以这样做:

[[ ! -f "$FILE" ]] && echo "File doesn't exist"

要么

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

如果要同时检查文件和文件夹,请使用 -e选项而不是 -f-e 对常规文件,目录,套接字,字符特殊文件,块特殊文件等返回true。


14
2018-04-28 19:40



这不是特定于bash的。语法来自Korn shell,也可以在zsh和bash中使用。它比标准的优势有限 [ 虽然在这里实用。 - Stephane Chazelas
定期 文件(由检查 -f)和 目录 只是众多不同类型中的两种 档。还有插座,符号链接,设备,FIFO,门...... [ -e 将测试文件存在(任何类型,包括常规,fifo,目录......) 符号链接解析后。 - Stephane Chazelas
@StephaneChazelas:我在任何地方都看不到任何ksh或zsh标签。我们谈论的是bash或大多数unix系统标准化的东西。因此,在上下文中,它是特定的bash。 OP不需要知道每个贝壳都在外面:D - Jahid
那是对评论的评论 “Bash具体” 部分答案。 - Stephane Chazelas
@StephaneChazelas:是的,在上下文中(bash和标准sh),它是特定的bash。我没有在你的第二条评论中看到这一点,它完全脱离了背景。 OP不需要 -e, 他需要 -f。 - Jahid


[ -f "$file" ]

[ 命令做了 stat() (不 lstat())系统调用存储在路径中 $file 并返回 真正 如果该系统调用成功并且返回的文件类型 stat() 是“正常的”。

因此,如果 [ -f "$file" ] 返回true,你可以告诉文件确实存在并且是一个常规文件或符号链接最终解析为一个常规文件(或者至少它是在 stat())。

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

  • 该文件不存在
  • 该文件存在但不是常规文件
  • 该文件存在,但您没有父目录的搜索权限
  • 该文件存在,但访问它的路径太长
  • 该文件是常规文件的符号链接,但您没有对符号链接解析中涉及的某些目录的搜索权限。
  • ......任何其他原因 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 失败后的特殊变量 [ 命令,并使用。解码该号码 $errnos 中的特殊数组 zsh/system 模块:

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;;
    (*) echo "can't tell"; syserror "$err"
  esac
fi

(小心 $errnos 某些版本的支持被破坏了 zsh 当使用最新版本构建时 gcc)。


13
2017-10-14 15:18



很好解释!一个侧面问题:这是Stephane Chazelas,发现壳震动的那个人? :) - Ali Haider
是的。 (我不能代表Stephane,但Stack Exchange中有其他条目,我已经看过它了,是的,这就是那个。) - Mike S