在Unix中打印两行之间的文本(来自文件中的行号列表)

时间:2013-02-23 13:47:12

标签: shell unix sed awk

我有一个包含数千行的示例文件。 我想在该文件中的两个行号之间打印文本。我不想手动输入行号,而是我有一个文件,其中包含必须打印文本的行号列表。

示例:linenumbers.txt

345|789
999|1056
1522|1366
3523|3562

我需要一个shell脚本,它将读取此文件中的行号,并将每行范围内的文本打印到一个单独的(新)文件中。

也就是说,它应该将345到789之间的行打印到一个新文件中,比如File1.txt,然后将行999和1056之间的文本打印到一个新文件中,比如说File2.txt,依此类推。

6 个答案:

答案 0 :(得分:2)

考虑到你的目标文件只有几千行。这是一个快速而肮脏的解决方案。

awk -F'|' '{system("sed -n \""$1","$2"p\" targetFile > file"NR)}' linenumbers.txt
  • targetFile是包含数千行的文件。
  • oneliner 要求对您的linenumbers.txt进行排序。
  • oneliner允许行范围在linenumbers.txt
  • 中重叠

运行上面的命令后,您将拥有n filex个文件。 n linenumbers.txt x的行数来自1-n您可以根据需要更改文件名模式。

答案 1 :(得分:2)

这是使用GNU awk的一种方式。像:

一样运行
awk -f script.awk numbers.txt file.txt

script.awk的内容:

BEGIN {
    # set the field separator
    FS="|"
}

# for the first file in the arguments list
FNR==NR {

    # add the row number and field one as keys to a multidimensional array with
    # a value of field two
    a[NR][$1]=$2

    # skip processing the rest of the code
    next
}

# for the second file in the arguments list
{
    # for every element in the array's first dimension
    for (i in a) {

        # for every element in the second dimension
        for (j in a[i]) {

            # ensure that the first field is treated numerically
            j+=0

            # if the line number is greater than the first field
            # and smaller than the second field
            if (FNR>=j && FNR<=a[i][j]) {

                # print the line to a file with the suffix of the first file's 
                # line number (the first dimension)
                print > "File" i
            }
        }
    }
}

或者,这是单行:

awk -F "|" 'FNR==NR { a[NR][$1]=$2; next } { for (i in a) for (j in a[i]) { j+=0; if (FNR>=j && FNR<=a[i][j]) print > "File" i } }' numbers.txt file.txt



如果你有一个'旧'awk,这里的版本是兼容性的。像:

一样运行
awk -f script.awk numbers.txt file.txt

script.awk的内容:

BEGIN {
    # set the field separator
    FS="|"
}

# for the first file in the arguments list
FNR==NR {

    # add the row number and field one as a key to a pseudo-multidimensional
    # array with a value of field two
    a[NR,$1]=$2

    # skip processing the rest of the code
    next
}

# for the second file in the arguments list
{
    # for every element in the array
    for (i in a) {

        # split the element in to another array
        # b[1] is the row number and b[2] is the first field 
        split(i,b,SUBSEP)

        # if the line number is greater than the first field
        # and smaller than the second field
        if (FNR>=b[2] && FNR<=a[i]) {

            # print the line to a file with the suffix of the first file's
            # line number (the first pseudo-dimension)
            print > "File" b[1]
        }
    }
}

或者,这是单行:

awk -F "|" 'FNR==NR { a[NR,$1]=$2; next } { for (i in a) { split(i,b,SUBSEP); if (FNR>=b[2] && FNR<=a[i]) print > "File" b[1] } }' numbers.txt file.txt

答案 2 :(得分:1)

您可以执行以下操作

# myscript.sh
linenumbers="linenumber.txt"
somefile="afile"
while IFS=\| read start  end ; do
    echo "sed -n '$start,${end}p;${end}q;' $somefile  > $somefile-$start-$end"
done < $linenumbers

sh myscript.sh

一样运行它
sed -n '345,789p;789q;' afile  > afile-345-789
sed -n '999,1056p;1056q;' afile  > afile-999-1056
sed -n '1522,1366p;1366q;' afile  > afile-1522-1366
sed -n '3523,3562p;3562q;' afile  > afile-3523-3562

然后当你开心的时候sh myscript.sh | sh

编辑在风格和正确性方面增加了威廉的优秀观点。

编辑解释

基本思想是获取一个脚本来生成一系列shell命令,这些命令可以在被“| sh”执行之前先检查其是否正确。

sed -n '345,789p;789q;表示使用sed并且不回显每一行(-n);有两个命令从第345行到第789页(rint)表示行,第二个命令在第789行(uit) - 通过退出保存的最后一行sed读取所有输入文件。

while循环使用read read从$ linenumbers文件中读取,如果给定多个变量名,每个变量名用输入中的字段填充,则字段通常用空格分隔如果变量名太少,那么read会将剩余数据放入最后一个变量名。

您可以在shell提示符下输入以下内容以了解该行为。

ls -l | while read first rest ; do
   echo $first XXXX $rest
done

尝试在上面添加另一个变量second,看看会发生什么,这应该是显而易见的。

问题是你的数据是由 | 分隔的,而当使用威廉的IFS=\|建议时,从IFS已经改变的输入中读取并且输入现在由 | 分隔,我们得到了所需的结果。

其他人可以随意编辑,更正和扩展。

答案 3 :(得分:1)

我会使用sed来处理示例数据文件,因为它简单而快捷。这需要一种将行号文件转换为适当的sed脚本的机制。有很多方法可以做到这一点。

一种方法是使用sed将行号集转换为sed脚本。如果一切都是标准输出,这将是微不足道的。由于输出需要转到不同的文件,我们需要行号文件中每行的行号。提供行号的一种方法是nl命令。另一种可能性是使用pr -n -l1。相同的sed命令行适用于:

nl linenumbers.txt |
sed 's/ *\([0-9]*\)[^0-9]*\([0-9]*\)|\([0-9]*\)/\2,\3w file\1.txt/'

对于给定的数据文件,生成:

345,789w > file1.txt
999,1056w > file2.txt
1522,1366w > file3.txt
3523,3562w > file4.txt

另一种选择是让awk生成sed脚本:

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt

如果您的sed版本允许您使用-f -标准输入读取其脚本(GNU sed确实; BSD sed没有),那么您可以将行号文件动态转换为sed脚本,并使用它来解析样本数据:

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt |
sed -n -f - sample.data

如果您的系统支持/dev/stdin,则可以使用以下方法之一:

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt |
sed -n -f /dev/stdin sample.data

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt |
sed -n -f /dev/fd/0 sample.data

如果失败,请使用显式脚本文件:

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt > sed.script
sed -n -f sed.script sample.data
rm -f sed.script

严格来说,您应该确保临时文件名是唯一的(mktemp)并删除即使脚本被中断(trap):

tmp=$(mktemp sed.script.XXXXXX)
trap "rm -f $tmp; exit 1" 0 1 2 3 13 15

awk -F'|' '{ printf "%d,%dw > file%d.txt\n", $1, $2, NR }' linenumbers.txt > $tmp
sed -n -f $tmp sample.data
rm -f $tmp
trap 0

最终trap 0允许您的脚本成功退出;省略它,脚本将始终以状态1退出。

我忽略了Perl和Python;要么可以在单个命令中使用它们。文件管理非常繁琐,使用sed似乎更简单。您也可以只使用awk,使用第一个awk脚本编写awk脚本来执行繁重的工作(上面的简单扩展),或者使用{{1}进程读取两个文件并生成所需的输出(更难,但远非不可能)。

如果不出意外,这表明有很多可能的方法来完成这项工作。如果这是一次性的练习,你选择的确无关紧要。如果您将反复执行此操作,请选择您喜欢的机制。如果你担心表现,请测量。将行号转换为命令脚本可能是一个可以忽略不计的成本;使用命令脚本处理样本数据是时间。我希望awk在那一点上表现出色;我没有测量确认它确实存在。

答案 4 :(得分:0)

要从345|789中提取第一个字段,您可以使用awk

awk -F'|' '{print $1}'

将其与您从其他问题收到的答案相结合,您将获得解决方案。

答案 5 :(得分:0)

这可能适合你(GNU sed):

sed -r 's/(.*)\|(.*)/\1,\2w file-\1-\2.txt/' | sed -nf - file