代码之家  ›  专栏  ›  技术社区  ›  johndashen

shell脚本:搜索/替换并检查文件是否存在

  •  0
  • johndashen  · 技术社区  · 16 年前

    我有一个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循环,但是我不知道该怎么做(pipes?临时文件?)

    作为一个额外的问题,有没有一种方法可以在bash grep中进行lookahead搜索?

    澄清/扩展问题:

    我有一堆程序,它们从诸如(但不一定)dat a/branch/special/pattern.xml之类的源获取输入,并将输出写入另一个目录results/special/branch-pattern.txt(或dat a/branch/intermediate/pattern.dat,例如)。如果该文件已经存在,我想签入我的作业管理shell脚本。

    例如,E转换数据/分支/特殊/模式.xml->结果/特殊/分支-.dat。我想查看输入的每个实例并检查输出是否存在。要做到这一点,一种(公认的简单方法)就是触摸每个输入文件旁边的*.d one文件并检查这些结果,但我宁愿不管理这些文件,有时工作终止不正确,因此我不希望它们被标记为“完成”。

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

    因此,解决上述问题(用伪代码)的一个简单、明确的方法可能是

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

    但我在找更一般的东西。

    5 回复  |  直到 16 年前
        1
  •  1
  •   Charles Duffy    16 年前
    #!/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 使用所有剩余的文件作为参数调用;如果是这样的话,从规范中就不清楚了。

    注意使用扩展的全局变量而不是解析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
    
        2
  •  1
  •   Jonathan Leffler    16 年前

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

     set -o noclobber
    

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

    似乎您希望在每个“.xml”文件上运行“jobservere”,而不需要匹配的“.txt”文件。您需要在这里评估TOCTU(检查时间、使用时间)问题,因为您处于集群环境中。但基本的想法可能是:

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

    这将与Korn Shell和Bash一起工作。在bash中,您可以探索将“todo”转换成一个数组;这样可以更好地处理文件名中的空格。

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

        3
  •  1
  •   slacker    16 年前
    whatsleft=$( ls *.xml *.txt | grep $PATTERN -o | sort | uniq -u )
    

    注意这实际上得到了 对称的 差异。

        4
  •  0
  •   ghostdog74    16 年前

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

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

    如果它不是你想要的,在你的问题中更清楚地描述你所说的“不要覆盖文件”是什么意思。

        5
  •  0
  •   johndashen    16 年前

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

    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;