Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/shell/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/.htaccess/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Bash shell脚本中的性能问题_Bash_Shell_Unix - Fatal编程技术网

Bash shell脚本中的性能问题

Bash shell脚本中的性能问题,bash,shell,unix,Bash,Shell,Unix,我有一个200 MB的标签分隔文本文件,有数百万行。在这个文件中,我有一个包含多个位置的专栏,如美国、英国、澳大利亚等 现在我想在这个专栏的基础上打破这个文件。虽然这段代码对我来说工作得很好,但面临性能问题,因为根据位置将文件拆分为多个文件需要1个多小时。代码如下: #!/bin/bash read -p "Please enter the file to split " file read -p "Enter the Col No. to split " col_no #set -x h

我有一个200 MB的标签分隔文本文件,有数百万行。在这个文件中,我有一个包含多个位置的专栏,如美国、英国、澳大利亚等

现在我想在这个专栏的基础上打破这个文件。虽然这段代码对我来说工作得很好,但面临性能问题,因为根据位置将文件拆分为多个文件需要1个多小时。代码如下:

#!/bin/bash

read -p "Please enter the file to split " file
read -p "Enter the Col No. to split " col_no

#set -x

header=`head -1 $file`

cnt=1
while IFS= read -r line
do
        if [ $((cnt++)) -eq 1 ]
        then
                echo "$line" >> /dev/null
        else
                loc=`echo "$line" | cut -f "$col_no"`
                f_name=`echo "file_"$loc".txt"`
                if [ -f "$f_name" ]
                then
                        echo "$line" >> "$f_name";
                else
                        touch "$f_name";
                        echo "file $f_name created.."
                        echo "$line" >> "$f_name";
                        sed -i '1i '"$header"'' "$f_name"
                fi
        fi

done < $file

有几件事需要记住:


  • 在读取时使用
    读取文件速度较慢
  • 创建子shell和执行外部进程很慢
  • 这是文本处理工具(如awk)的作业

    我建议您使用类似以下内容:

    # save first line
    NR == 1 {
        header = $0
        next
    }
    
    {
        filename = "file_" $col  ".txt"
    
        # if country code has changed
        if (filename != prev) {
            # close the previous file
            close(prev)
            # if we haven't seen this file yet
            if (!(filename in seen)) {
                print header > filename
            }
            seen[filename]
        }
    
        # print whole line to file
        print >> filename
        prev = filename
    }
    
    awk -F: -v col=4 -f script.awk file
    
    mkdir -p /tmp/i
    awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
    
    使用以下内容运行脚本:

    awk -v col="$col_no" -f script.awk file
    
    其中,
    $col_no
    是一个shell变量,包含带有国家/地区代码的列号

    如果您没有太多不同的国家/地区代码,您可以不必打开所有文件,在这种情况下,您可以删除对
    close(filename)
    的调用

    您可以在问题中提供的示例上测试脚本,如下所示:

    # save first line
    NR == 1 {
        header = $0
        next
    }
    
    {
        filename = "file_" $col  ".txt"
    
        # if country code has changed
        if (filename != prev) {
            # close the previous file
            close(prev)
            # if we haven't seen this file yet
            if (!(filename in seen)) {
                print header > filename
            }
            seen[filename]
        }
    
        # print whole line to file
        print >> filename
        prev = filename
    }
    
    awk -F: -v col=4 -f script.awk file
    
    mkdir -p /tmp/i
    awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
    

    请注意,我添加了
    -F:
    以将输入字段分隔符更改为

    我认为Tom的做法是正确的,但我会稍微简化一下

    Awk在某些方面很神奇。其中一种方法是,它将保持其所有输入和输出文件句柄处于打开状态,除非您显式关闭它们。因此,如果您创建了一个包含输出文件名的变量,您可以简单地重定向到您的变量,并相信awk会将数据发送到您指定的位置,并在输出文件的处理输入不足时最终关闭输出文件

    (注意:这个魔法的一个扩展是,除了重定向之外,您还可以维护多个管道。想象一下,如果您要
    cmd=“gzip-9>文件“$4”.txt.gz”;print | cmd

    下面将拆分文件,而不向每个输出文件添加头

    awk -F: 'NR>1 {out="file_"$4".txt"; print > out}' inp.txt
    
    如果添加标题很重要,则需要更多的代码。但不多

    awk -F: 'NR==1{h=$0;next} {out="file_"$4".txt"} !(out in files){print h > out; files[out]} {print > out}' inp.txt
    
    或者,因为这一行现在有点长,我们可以将其拆分出来进行解释:

    awk -F: '
      NR==1 {h=$0;next}        # Capture the header
      {out="file_"$4".txt"}    # Capture the output file
      !(out in files){         # If we haven't seen this output file before,
        print h > out;         # print the header to it,
        files[out]             # and record the fact that we've seen it.
      }
      {print > out}            # Finally, print our line of input.
    ' inp.txt
    
    我使用您在问题中提供的输入数据成功地测试了这两个脚本。使用这种类型的解决方案,不需要对输入数据进行排序——每个文件中的输出将按照子集记录在输入数据中出现的顺序进行

    注意:不同版本的
    awk
    将允许您打开不同数量的打开文件。GNU awk(
    gawk
    )有数千个上限,远远超过你可能要面对的国家数量。BSD awk版本20121220(在FreeBSD中)似乎在21117个文件之后用完。BSD awk版本20070501(在OS X El Capitan中)限制为17个文件

    如果您对打开的文件的潜在数量没有信心,可以使用以下方法尝试您的awk版本:

    # save first line
    NR == 1 {
        header = $0
        next
    }
    
    {
        filename = "file_" $col  ".txt"
    
        # if country code has changed
        if (filename != prev) {
            # close the previous file
            close(prev)
            # if we haven't seen this file yet
            if (!(filename in seen)) {
                print header > filename
            }
            seen[filename]
        }
    
        # print whole line to file
        print >> filename
        prev = filename
    }
    
    awk -F: -v col=4 -f script.awk file
    
    mkdir -p /tmp/i
    awk '{o="/tmp/i/file_"NR".txt"; print "hello" > o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
    
    还可以测试开放管道的数量:

    awk '{o="cat >/dev/null; #"NR; print "hello" | o; printf "\r%d ",NR > "/dev/stderr"}' /dev/random
    
    (如果您有一个
    /dev/yes
    或是一些只会吐出一行行令人厌恶的文本的东西,那么这比使用/dev/random进行输入要好。)


    我以前在自己的awk编程中没有遇到过这个限制,因为当我需要创建许多输出文件时,我总是使用gawk:-P

    在读取时使用
    读取文件和执行子进程都很慢。这是文本处理工具(如awk)的作业。您可以在单个awk脚本中完成整个任务,这将更加高效。您需要向我们展示一个简单的示例,其中包含一些输入/输出示例。您为什么不在任何地方打印某些内容(
    /dev/null
    )?
    f_name=`echo“file_u$loc.txt”`
    太难了,也没用:使用
    f_name=“file_$loc.txt”
    相同的句子(
    echo“$line”>>“$f_uname”;
    )在IF-THEN-ELSE的两个部分中都可以找到。您可以在IF之后移动它,然后在我出错时更正我,但您希望根据位置将数据拆分为不同的文件,这样您将拥有
    data_UK.txt
    data_US.txt
    等。?在对位置名称运行
    uniq
    后,您是否考虑过
    awk
    for
    循环?我想会快得多。我添加了一个冒号生成的示例数据。所以国家代码的列号是4Nice idea,带有
    文件
    数组,+1