PHP unlink() 处理异常

45

我一直在想,我是否能够正确地使用unlink()函数。如果无法删除文件(可能是由于文件不存在),我不希望unlink()函数抛出一些讨厌的错误。

我尝试了以下代码:

try { 
    unlink("secret/secret.txt"); 
} catch(Exception $e) { 
    print "whoops!"; 
    //or even leaving it empty so nothing is displayed
} 

但它没能正常工作。我不是PHP专家。我在网上搜索并找到了这个异常处理代码,但据我记得我在学校时,同样的代码用于Java。所以它应该可以工作。我不知道代码出了什么问题。

或者我可以简单地使用if...else语句,如:

if(unlink($file)){
  //leaving here empty must ensure that nothing is displayed
}else{
  //leaving here empty must ensure that nothing is displayed
}

但是这段代码也没有起作用。我错在哪里?有哪些其他正确的处理方式?

能否通过操作错误报告(PHP)(生产环境和开发环境)来隐藏错误?


4
PHP中并非所有API都实现了异常处理机制,如果无法删除文件,则unlink()函数将返回FALSE。使用if/else是正确的方法。 - Michael Berkowski
3
在使用unlink删除文件之前,也许应该先使用file_exists进行测试。 - tradyblix
2
测试文件是否存在,确保它是一个文件,并且您有删除权限。我不喜欢使用@来抑制错误,但我更不喜欢错误。 if ( is_file( $uri ) && is_writable( $uri ) ) { @unlink( $uri ); } - Juniper Jones
9个回答

49
注意:这种方法可能已经不再起作用,请查看Brian的评论。

如果你只想抑制错误,可以这样做:

@unlink('your_file_name');

一般来说,在PHP中,@符号可以抑制任何错误。

更好的方法是尽量减少错误的可能性。你已经说过其中一个错误可能是由于文件不存在引起的。如果我是你,我会这样做:

if(file_exists('your_file_name')){
    unlink('your_file_name');
}else{
    echo 'file not found';
}

祝你好运 :)


1
True会抑制任何错误...这并不是一件好事...-1 - prodigitalson
2
是的,除非建议使用“@”是一个不好的主意。你最好编写正确的代码而不是隐藏错误。 - tradyblix
3
问题在于存在竞争条件,即当调用file_exists时文件可能已经存在,但随后另一个进程在调用unlink函数之前删除了该文件。因此,在这种情况下最好使用 @ 符号。 - Colin D Bennett
我的建议是,在调试时,将“@”替换为“/@/”。 - FluorescentGreen5
在我看来,@unlink(..)似乎根本没有抑制unlink警告,至少在PHP 7.4.20中是这样。 我相当有信心过去我们没有收到警告。 - Brian C
显示剩余5条评论

29

unlink 不会抛出异常,它会生成错误。正确的方法是在尝试调用 unlink 之前检查文件是否存在。如果你只是担心没有输出错误,那么你应该在生产环境中关闭 display_errors(这本来就应该做),然后错误信息将被记录下来。

不要用 @ 来抑制错误,这很少是明智的做法。

你能更详细地描述一下 @ 吗?

我不确定你具体指的是什么。但文档在 这里。至于为什么不想使用它... 那是因为你永远不会知道代码是否正常工作或存在问题。即使代码从功能角度仍然有效,它仍然存在问题,并且这个问题可能会在某些时候完全阻碍其他操作。如果你从来没有错误信息,你可能会浪费很多时间进行调试。

改变日志级别或禁用错误显示都没问题,但你永远不要完全压制错误信息。


谢谢你,非常感谢。你的回答也帮助我理解了@的概念。你能更详细地解释一下@吗? - prakashchhetri
23
在高流量的网站上,即使使用if (file_exists($path)) unlink($path);语句,仍然会出现错误,因为另一个进程在检查/删除之间已经删除了该文件。 - Abhi Beckert
1
检查文件是否存在的问题在于,如果另一个进程正在使用该文件,则仍将显示警告。 - Alan Deep
2
你仍然可以使用“@”,但之后需要检查错误和“FALSE”返回值。 - siride

24
这种方法可能看起来有些奇怪,但我认为它是最可靠的方法,它考虑了“竞态条件”。

is_file

if(is_file($file) && @unlink($file)){
    // delete success
} else if (is_file ($file)) {
    // unlink failed.
    // you would have got an error if it wasn't suppressed
} else {
  // file doesn't exist
}

为什么?

首先,is_file 是正确的方法来检查文件是否存在,而不是 file_existsfile_exists 检查目录和文件,因此可能会对具有相同文件名的目录返回 TRUE,您不能使用 unlink 删除目录,否则会引发错误。

在删除文件之前(unlink),检查文件是否存在(is_file)是正确/最佳的方法。

if(is_file($file) && unlink($file)){

但这并不是一种完美的方法,因为在is_file检查和unlink之间的短暂时间内,文件通常会被删除。当缓存方法使用文件系统时,我已经多次遇到过这种情况。

但这是目前最好的方法。

所以你可以做到一切都正确,但仍然会出现错误!

好吧,至少错误会告诉你它失败了……实际上,你可以在没有错误的情况下知道它是否失败。

unlink

成功时返回TRUE,失败时返回FALSE

如果你已经编写了正确的代码,并且能够区分成功和失败的unlink,那么是的,请抑制错误,因为它对你或你的代码没有任何好处。

无论错误是否被抑制,这是我能想到的最好方法来防止它发生。通过缩短检查和删除之间的时间,您将减少它抛出错误的可能性。

编辑:更新链接URL


2
不需要两次使用 is_file,更为简单:if (is_file($file)) { if (@unlink($file)) { ...success... } else { ...unlink failed...} } else { ...file doesn't exist... } - ToolmakerSteve
2
@ToolmakerSteve 通常情况下,您是正确的,但在这种情况下,我认为额外的检查是必要的,因为在此答案和其他答案中提到了竞态条件。如果unlink()失败,可能是因为文件在第一次检查时存在,但在调用unlink()之前的短时间窗口内被删除。因此,再次调用is_file()是必要的,以询问即使在unlink()失败后,文件是否仍然存在,这将让我们知道文件是否不存在或是否存在应引起更多关注的错误。 - Chris Morbitzer
@ChrisMorbitzer - 我理解你的观点。如果您不需要/不想区分“失败但文件已经消失”(意思是在您尝试删除它时它消失了)和“文件从未存在”,那么答案中的代码就很好。而我的建议的代码会说“unlink failed”,即使现在已经不存在-可能不好这样说。最好仔细注释代码,以解释为什么要这样做。另一方面,如果想记录竞争条件发生的情况,则可以将第二个is_file添加到我的版本中,在...unlink failed...分支中。 - ToolmakerSteve

7
你可以使用 is_writable 函数来测试你是否拥有合适的权限来修改或删除一个文件。 http://php.net/manual/zh/function.is-writable.php
try {
  if(!is_writable($file))
      throw new Exception('File not writable');

  unlink($file);
}
catch(Exception $e) { /* do what you want */ }

1
虽然这是一个好主意,但 is_writable() 只告诉你文件本身是否可写。在 Linux(我想大概也是在 Windows 上),你需要对包含文件的实际文件夹具有写入权限才能删除它,而不是文件本身。事实上,在 Linux 上,只要你对文件夹具有写入权限,即使你不能写入文件(不确定 Windows 是否也是如此),你也可以愉快地删除它们。 - Brian C

5

我的经验告诉我,在调用unlink()之前,即使在调用file_exists()clearstatcache()之后,file_exists()的调用也无法生效。

根据PHP版本和操作系统的不同,有许多组合方式。然而,我发现唯一总是有效的方法(即,在出现错误时避免显示警告消息)是创建自己的函数silent_unlink()

function silent_unlink( $filename )
{
  $old_er = error_reporting();
  error_reporting( $old_er & ~E_WARNING );
  $result = unlink( $filename );
  error_reporting( $old_er );
  return $result;
}

它禁用警告的错误报告,只针对调用 unlink(),并恢复先前的 error_reporting() 状态。


1
这不就相当于在 unlink 上加上 "@" 吗?你遇到过 "@" 无法抑制错误的情况吗?另一方面,我认为调用这样的自定义函数的一个好处是可以轻松进行自定义日志记录或在开发期间设置断点。但是对于生产环境,为什么不将方法体变成一行:return @unlink( $filename ); - ToolmakerSteve
2
不,@ 对我没有用。它对其余的函数有效,但对unlink无效。上次我检查时,我使用的是PHP 5.4在Apache 2.4 Windows上工作。正如所说,我尝试了很多方法(使用@是我的首选),但提到的函数是唯一可行的方式。在每个测试服务器上都是如此。 - VCorrea

2

使用try catch处理unlink()抛出的"资源不可用"错误

即使is_file()file_exists()检查文件是否存在,仍有可能会出现由于某些应用程序正在使用该文件而无法删除,导致unlink()抛出"资源不可用"错误。

因此,在尝试了许多方法(如:is_resource()is_writable()stream_get_meta_data()等)之后,我找到了唯一处理"删除"不存在或已被某个应用程序使用的文件时出错的最佳方法。

function delete_file($pFilename)
{
    if ( file_exists($pFilename) ) { 
        //  Added by muhammad.begawala@gmail.com
        //  '@' will stop displaying "Resource Unavailable" error because of file is open some where.
        //  'unlink($pFilename) !== true' will check if file is deleted successfully.
        //  Throwing exception so that we can handle error easily instead of displaying to users.
        if( @unlink($pFilename) !== true )
            throw new Exception('Could not delete file: ' . $pFilename . ' Please close all applications that are using it.');
    }   
    return true;
}

=== 使用方法 ===

try {
    if( delete_file('hello_world.xlsx') === true )
        echo 'File Deleted';
}
catch (Exception $e) {
    echo $e->getMessage(); // will print Exception message defined above.
}

1

1
if (file_exists($file) && is_writable($file)) {
    return  unlink($file);
}

1
据说错误抑制运算符很昂贵,它有一个潜在的缺点,即隐藏意外错误,而且它解决的问题几乎总有更好的解决方案。
但在这种情况下,我们已经在做一个不太平凡的I/O操作(因此增加的处理时间可能并不是一个大问题),我们正在调用一个单独的内置函数(限制了错误掩盖问题),并且没有办法防止警告触发。如果我们能捕获警告消息,或许我们就有一个合理使用@的案例:
$success = @unlink($path);
$errorInfo = error_get_last();
if ($success === false) {
    $error = $errorInfo['message'] ?? 'unlink() failed';
    throw new \RuntimeException("Failed to delete file $path: $error");
}

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