检查文件是否存在的Shell脚本

8

我正在尝试编写一个简单的脚本,以判断$Temp中是否存在以字符串“Test”开头的文件名。

例如,我有以下这些文件:

Test1989.txt
Test1990.txt
Test1991.txt

我只想输出“找到文件”的信息。
例如,像这样的信息:
file="home/edward/bank1/fiche/Test*"
if  test -s "$file" 
then 
    echo "found one"
else 
    echo "found none"
fi

但这并不奏效。


这个问题指定了“shell”——是哪一个?我认为最好的答案是针对bash的;这合适吗,还是你的目标是其他东西? - Charles Duffy
7个回答

8

一种方法:

(
  shopt -s nullglob
  files=(/home/edward/bank1/fiche/Test*)
  if [[ "${#files[@]}" -gt 0 ]] ; then
    echo found one
  else
    echo found none
  fi
)

解释:

  • shopt -s nullglob 会导致 /home/edward/bank1/fiche/Test* 如果没有文件匹配该模式,就会被扩展为 。(如果不设置,则保持不变。)
  • ( ... ) 设置了一个子shell,防止 shopt -s nullglob 被“逃脱”。
  • files=(/home/edward/bank1/fiche/Test*) 把文件列表放在名为 files 的数组中。(请注意,这仅在子shell中;files 在子shell退出后将不可访问。)
  • "${#files[@]}" 是此数组中元素的数量。

编辑以回答 后续问题(“如果我还需要检查这些文件是否有数据,并且不是零字节文件怎么办”):

对于这个版本,我们需要使用-s(正如您在问题中所做的那样),它也测试文件是否存在,因此不再需要使用shopt -s nullglob:如果没有文件匹配该模式,则模式上的-s将为false。因此,我们可以写成:

(
  found_nonempty=''
  for file in /home/edward/bank1/fiche/Test* ; do
    if [[ -s "$file" ]] ; then
      found_nonempty=1
    fi
  done
  if [[ "$found_nonempty" ]] ; then
    echo found one
  else
    echo found none
  fi
)

(这里的( ... )是为了防止filefound_file“逃脱”。)

如果我还需要检查这些文件是否包含数据并且不是零字节文件,该怎么办? - Edward
然后你需要编写一个循环,如 for file in "${files[@]}" - Barmar
这是一个很好的答案,我已经点赞了——我的唯一争议是使用子shell是不必要的,并且鼓励不必要的fork()并没有必要。如果目标是避免污染全局命名空间,则创建一个函数并使用本地变量就足够了。 - Charles Duffy

7

你需要了解Unix如何解释你的输入。

标准的Unix shell在将参数传递给程序之前插值环境变量和所谓的通配符,这与Windows有些不同,因为它使程序解释扩展。

尝试一下:

 $ echo *

这将回显您当前目录中的所有文件和目录。在 echo 命令执行之前,Shell 插值并扩展了 *,然后将扩展后的参数传回给您的命令。您可以通过执行以下命令来查看其工作方式:

$ set -xv
$ echo *
$ set +xv
set -xv 命令可以开启 xtraceverbose。Verbose 会输出输入的命令,而 xtrace 则会输出经过 shell 展开后即将执行的命令。现在试一下:
$ echo "*"

请注意,将某些内容放入引号中可以隐藏来自shell的glob表达式,shell无法展开它。请尝试以下操作:
$ foo="this is the value of foo"
$ echo $foo
$ echo "$foo"
$ echo '$foo'

请注意,Shell 仍然可以在双引号中扩展环境变量,但不能在单引号中扩展。
现在让我们看一下您的语句:
file="home/edward/bank1/fiche/Test*"

双引号可以避免 shell 展开“glob 表达式”,因此变量“file”等于字面值“home/edward/bank1/finche/Test*”。因此,您需要执行以下操作:
file=/home/edward/bank1/fiche/Test*

缺少引号(和重要的斜杠!)现在将使文件等于与该表达式匹配的所有文件。 (可能不止一个!)如果没有文件,根据shell及其设置,shell可能仍将文件设置为该文字字符串。
你肯定有正确的想法:
 file=/home/edward/bank1/fiche/Test*
 if  test -s $file
    then 
        echo "found one"
    else 
       echo "found none"
 fi

然而,如果有多个文件,您仍然可能会得到 未找到 的返回结果。相反,您的 test 命令可能会因为参数过多而出现错误。

解决这个问题的方法之一是:

if ls /home/edward/bank1/finche/Test* > /dev/null 2>&1
then
    echo "There is at least one match (maybe more)!"
else
    echo "No files found"
fi

在这种情况下,我利用了ls命令的退出代码。如果ls找到一个可以访问的文件,它将返回零退出代码。如果它不能找到匹配的文件,则返回非零退出代码。 if命令仅执行命令,然后如果命令返回零,则假定if语句为true并执行if子句。如果命令返回非零值,则假定if语句为false,并执行else子句(如果有的话)。 test命令以类似的方式工作。如果test为真,则test命令返回零。否则,test命令返回一个非零值。这与if命令非常搭配。事实上,有一个别名test命令。尝试这个:
 $ ls -li /bin/test /bin/[

i 命令会打印出 inodeinode 是文件的真实 ID。具有相同 ID 的文件是相同的文件。你可以看到,/bin/test/bin/[ 是相同的命令。这使得以下两个命令相同:

if test -s $file
then
    echo "The file exists"
fi

if [ -s $file ]
then
    echo "The file exists"
fi

3
你可以用一行代码实现它:
ls /home/edward/bank1/fiche/Test* >/dev/null 2>&1 && echo "found one" || echo "found none"

要理解它的作用,您需要分解命令,并具有布尔逻辑的基本意识。

直接从bash man页面:

[...]
expression1 && expression2
     True if both expression1 and expression2 are true.
expression1 || expression2
     True if either expression1 or expression2 is true.
[...]

在shell(以及Unix世界中),布尔值true是一个以状态0退出的程序。 ls试图列出模式,如果成功(意味着模式存在),它将以状态0退出,否则为2(详细信息请查看ls手册页)。
在我们的例子中实际上有3个表达式,为了清晰起见,我会加上括号,尽管这不是必需的,因为&&具有优先于||的优先级。
 (expression1 && expression2) || expression3

因此,如果表达式1为真(即:ls找到了模式),则评估表达式2(这只是一个echo并将以状态0退出)。在这种情况下,表达式3从未被评估,因为||左侧的内容已经为true,尝试评估右侧的内容将是浪费资源。

否则,如果表达式1为假,则不评估表达式2,但在这种情况下会评估表达式3。


2
这是不必要的昂贵。ls 是一个外部程序,而不是 shell 内置程序,因此调用它会产生分叉。Shell 可以在不调用任何外部程序的情况下检查文件 -- 毕竟,glob 表达式就是这样做的。此外,ls 花费时间和 CPU 对其参数进行排序,打印它们(即使只是到 /dev/null)等。 - Charles Duffy

1
for entry in "/home/loc/etc/"/*
do

   if [ -s /home/loc/etc/$entry ]
   then
       echo "$entry File is available"
   else
       echo "$entry File is not available"
fi
done

Hope it helps

0

检查文件是否存在的一行代码 -

awk 'BEGIN {print getline < "file.txt" < 0 ? "File does not exist" : "File Exists"}'

OP 需要在特定目录中检查给定名称模式的文件,并确保该文件非空。 - blackpen

0
以下脚本将帮助您进入一个过程,如果该脚本存在于指定的变量中,返回翻译文本。
cat > waitfor.csh

    #!/bin/csh

    while !( -e $1 )

    sleep 10m

    end

ctrl+D

-e 在这里是用于文件操作的。

$1 是一个 shell 变量,

等待 10 分钟。

你可以通过 ./waitfor.csh ./temp ; echo "the file exits" 来执行脚本。


-1

通配符不会在引号内扩展。当通配符被扩展时,如果没有匹配项,则返回未更改的通配符,而不是扩展为空字符串。请尝试:

output="$(ls home/edward/bank1/fiche/Test* 2>/dev/null)"
if [ -n "$output" ]
then echo "Found one"
else echo "Found none"
fi

如果通配符扩展到文件名,则ls将在stdout上列出它们;否则将在stderr上打印错误,且不会在stdout上输出任何内容。 stdout的内容被分配给outputif [-n "$output"]测试$output是否包含任何内容。
另一种编写此内容的方式是:
if [ $(ls home/edward/bank1/fiche/Test* 2>/dev/null | wc -l) -gt 0 ]

“-n” 代表什么意思? - Edward
如果 <string> 不为空,则 test -n <string> 为真。 - Barmar
如果有多个匹配的文件,使用 ls 在这里将无法正常工作。最好使用通配符表达式。请参见http://mywiki.wooledge.org/BashFAQ/004 - Charles Duffy
@CharlesDuffy 为什么它不能工作?我们只是测试ls是否产生了任何输出,而不检查它是否是实际的文件名。我已经将变量名称更改为不那么具有误导性的名称。 - Barmar
啊——我把它读成了 [ -e "$output" ],而不是 -n。是的,那样可以工作……尽管这是不必要的低效率。当你只想知道一个文件是否存在时,为什么要使用一个外部工具(在子shell中调用!)生成排序、格式化的人类可读输出呢?你可以在没有任何fork的情况下将glob表达式的输出记录在一个数组中——并且shell必须运行该glob表达式来构建ls的参数列表,因此它是开销的严格子集。 - Charles Duffy
@CharlesDuffy 我知道,ruakh的答案展示了那种方法。当我写我的答案时,我没有想到那种方法。 - Barmar

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