【问题标题】:shell scripting: search/replace & check file existshell脚本:搜索/替换和检查文件是否存在
【发布时间】:2010-04-16 23:09:52
【问题描述】:

我有一个 perl 脚本(或任何可执行文件)E,它将获取文件 foo.xml 并写入文件 foo.txt。我使用 Beowulf 集群为大量 XML 文件运行 E,但我想在 shell (bash) 中编写一个简单的作业服务器脚本,它不会覆盖现有的 txt 文件。

我目前正在做类似的事情

#!/bin/sh
PATTERN="[A-Z]*0[1-2][a-j]"; # this matches foo in all cases 
todo=`ls *.xml | grep $PATTERN -o`;
isdone=`ls *.txt | grep $PATTERN -o`;

whatsleft=todo - isdone; # what's the unix magic?

#tack on the .xml prefix with sed or something

#and then call the job server; 
jobserve E "$whatsleft";

然后我不知道如何区分 $todo 和 $isdone。我更喜欢使用 sort/uniq,而不是像里面有 grep 的 for 循环,但我不知道该怎么做(管道?临时文件?)

作为一个额外的问题,有没有办法在 bash grep 中进行前瞻搜索?

澄清/扩展问题:

我有一堆程序从(但不一定)data/{branch}/special/{pattern}.xml 等源获取输入并将输出写入另一个目录 results/special/{branch}-{pattern} .txt(或数据/{branch}/intermediate/{pattern}.dat,例如)。如果该文件已存在,我想检查我的 jobfarming shell 脚本。

例如,E 转换 data/{branch}/special/{pattern}.xml->results/special/{branch}-{pattern}.dat。我想查看输入的每个实例并检查输出是否存在。一种(公认更简单)的方法是触摸每个输入文件旁边的 *.done 文件并检查这些结果,但我宁愿不管理这些,有时作业会不正确地终止,所以我不想要它们标记完成。

注意我不需要检查并发性或锁定任何文件。

所以解决上述问题的一种简单明了的方法(在伪代码中)可能是

for i in `/bin/ls *.xml`
do
   replace xml suffix with txt
   if [that file exists]
      add to whatsleft list
   end
done

但我正在寻找更通用的东西。

【问题讨论】:

  • txtfile=${xmlfile%.xml}.txt 替换 - 正如我的回答一样。
  • 当你说“避免覆盖文件”时——我们需要并发意识吗?如果是这样,我们需要做一些锁定。 (如果是这样的话……我们是在共享文件系统上吗?哪个?它对flock 有适当的语义吗?)
  • 没有并发意识,还没有锁定 - 它是一个共享文件系统,但现在这是一个副项目
  • 顺便说一句 - 如果您致力于使问题更加细化和独立,它将有助于 StackOverflow 作为知识库的质量。例如,“我如何获得以扩展名 A 开头而不是扩展名 B 的文件列表”,它很小且可重复使用;一旦问题包含有关您的特定用例的大量详细信息,其他人就更难找到并且更少使用。

标签: shell scripting grep replace


【解决方案1】:
#!/bin/sh

shopt -s extglob # allow extended glob syntax, for matching the filenames

LC_COLLATE=C     # use a sort order comm is happy with

IFS=$'\n'        # so filenames can have spaces but not newlines
                 # (newlines don't work so well with comm anyhow;
                 # shame it doesn't have an option for null-separated
                 # input lines).

files_todo=( **([A-Z])0[1-2][a-j]*.xml )
files_done=( **([A-Z])0[1-2][a-j]*.txt )
files_remaining=( \
  $(comm -23 --nocheck-order \
    <(printf "%s\n" "${files_todo[@]%.xml}") \
    <(printf "%s\n" "${files_done[@]%.txt}") ))

echo jobserve E $(for f in "${files_remaining[@]%.xml}"; do printf "%s\n" "${f}.txt"; done)

这假设您想要一个带有所有剩余文件作为参数的jobserve E 调用;如果是这种情况,规范中还不清楚。

注意使用扩展glob而不是解析ls,即considered very poor practice

要将输入转换为输出名称而不使用 shell 内置函数以外的任何内容,请考虑以下事项:

if [[ $in_name =~ data/([^/]+)/special/([^/]+).xml ]] ; then
  out_name=results/special/${BASH_REMATCH[1]}-${BASH_REMATCH[2]}.dat
else
  : # ...handle here the fact that you have a noncompliant name...
fi

【讨论】:

  • 看起来很棒。我不知道 IFS 或 comm。你能解释一下 shopt 和 LC_COLLATE 行的作用吗?
  • shopt 行设置了extglob 标志,它允许我们使用扩展的 glob 语法匹配文件(实际上,我正在做的只是匹配没有正则表达式的相关文件)。 LC_COLLATE=C 正在将默认排序顺序(在这种情况下,对于 globbed 文件)设置为 comm 会满意的东西。
  • 关于ls 的优点。虽然我认为用find 替换它会更简单,更易读。
  • 您能否将其扩展到文件中的多个模式匹配,例如从 data/{branch}/special/{pattern}.xml->results/archive/{branch}-{pattern}.dat,如果您只是更改内部 printf 语句?您不必为此再次显示整个示例代码。
  • @johndashen - 对不起,我不太明白你在这里要求什么。您是要从文件中选择分支名称(用于其他名称),还是选择仅具有特定分支名称的文件或其他名称?
【解决方案2】:

问题标题表明您可能正在寻找:

 set -o noclobber

问题内容表示完全不同的问题!

您似乎想在没有匹配的“.txt”文件的情况下对每个“.xml”文件运行“jobserve E”。您需要在此处评估 TOCTOU(检查时间,使用时间)问题,因为您处于集群环境中。但基本的想法可能是:

 todo=""
 for file in *.xml
 do [ -f ${file%.xml}.txt ] || todo="$todo $file"
 done
 jobserve E $todo

这将适用于 Korn shell 以及 Bash。在 Bash 中,您可以探索将“待办事项”制作成数组;这将比这更好地处理文件名中的空格。

如果您在运行此检查时仍有进程为“.xml”文件生成“.txt”文件,您将得到一些重复的工作(因为此脚本无法判断正在处理)。如果“E”进程在开始处理它时创建了相应的“.txt”文件,则可以最大限度地减少机会或重复工作。或者,也许考虑将已处理的文件与未处理的文件分开,因此“E”进程将“.xml”文件从“to-be-done”目录移动到“done”目录(并写入“.txt”文件也到“完成”目录)。如果仔细完成,这可以避免大多数多处理问题。例如,您可以在处理开始时将 '.xml' 链接到 'done' 目录,并确保使用 'atexit()' 处理程序进行适当的清理(如果您有信心处理程序不会崩溃)。或者你自己设计的其他诡计。

【讨论】:

  • 这对我有用,因为脚本 E 不会在调用之间访问任何重叠文件。我有几个后续问题,因为我对 bash 脚本还很陌生:(1)我可以在 for-in 子句中使用带有多个星号的 glob 吗?如 */special/*.xml? (2) % 语法是否会删除所有 .xml 的实例?
  • (1) 是; (2) 否。单个 % 仅删除最后一个“.xml”(因此 x.xml.xml.xml --> x.xml.xml)。
【解决方案3】:
whatsleft=$( ls *.xml *.txt | grep $PATTERN -o | sort | uniq -u )

请注意,这实际上有一个对称差异。

【讨论】:

  • 这在示例中对我有用,但我稍微简化了它:我想让它也适用于不同的模式,例如来自 *.xml -> *-reordered.xml ,以及跨目录。在这种情况下,我使用 ls 和 --ignore: 你能修改你的命令来适应它吗?
  • @johndashen:我不明白为什么它不起作用,或者我只是不明白你的意思:)。能不能解释的更清楚一些,最好举个例子?
  • 如果我将您示例中的 *.txt 替换为 *-reordered.xml,我将始终获得 *-reordered.xml 的副本两次......但 uniq 会处理这个问题,所以它不是实际上是一个问题。嗯。 =)
【解决方案4】:

我不确定你想要什么,但你可以先检查文件是否存在,如果存在,创建一个新名称? (或者在您的 E(perl 脚本)中执行此检查。)

if [ -f "$file" ];then
  newname="...."
fi
...
jobserve E .... > $newname 

如果不是您想要的,请在您的问题中更清楚地描述“不要覆盖文件”是什么意思..

【讨论】:

  • 这是我想要的行为,但我不想指望 perl 脚本/可执行文件来防止覆盖。
【解决方案5】:

为了后代,这就是我发现的工作:

TMPA='neverwritethis.tmp'
TMPB='neverwritethat.tmp'
ls *.xml | grep $PATTERN -o > $TMPA;
ls *.txt | grep $PATTERN -o > $TMPB;
whatsleft = `sort $TMPA $TMPB | uniq -u | sed "s/%/.xml" > xargs`;
rm $TMPA $TMPB;

【讨论】:

  • 如果 $TMPA 和 $TMPB 真的是命名管道会更酷。
  • 查看我给出的答案,它不需要临时文件,并且只使用单个外部命令(comm)而不是那里(sortuniqsed) .
猜你喜欢
  • 1970-01-01
  • 2020-04-29
  • 1970-01-01
  • 2016-05-14
  • 1970-01-01
  • 2017-09-11
  • 2011-03-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多