【问题标题】:Splitting a text file using a separate file of line numbers [duplicate]使用单独的行号文件拆分文本文件[重复]
【发布时间】:2021-01-26 00:16:52
【问题描述】:

我有一个文本文件,其中包含多个 (~80) 个连接在一起的 OneNote 页面,我试图将其拆分为每个页面的组件文件。我试图通过页面标题的行号来做到这一点,因为页面的长度是可变的,虽然我已经能够将行号提取到一个单独的文件中,但我无法弄清楚如何与他们分手。例如

日志.txt:

Tuning             //Page Title
09 November 2016   //Date
23:19              //Time
 
Content text...    //Page Content
 
Week 46            //Another title, want to split here
14 November 2016
13:47
 
Text..
More text...       //Content can be over multiple lines

Week 47            //Another title, want to split here
22 November 2016
11:15

Text
etc...

单独文件中的行号: Lines.txt:

1
7
14

此示例中的预期输出将提供三个文件,每个文件从页面标题向下到下一页标题之前的最后一行。

log1.txt log2.txt log3.txt

$ cat log1.txt
Tuning             
09 November 2016
23:19

Content text...

$

我找到了很多关于拆分为固定块(例如每 50 行)的答案,因为这些部分的长度可变,所以在这里不起作用。大多数以固定行号拆分的人只处理几个可以硬编码的行号,例如using head or tail commands.

This answer 非常接近我正在寻找的内容,但是要拆分的行号的输入再次非常小,可以直接写入命令中。我不知道如何使用行号文件代替将其写入字符串“1 7 14”等。

我在 macos 上使用 bash,并且对命令行中的这一级别的工作非常陌生,并且没有使用 grep、sed、awk 等的实际经验,所以我很难概括这个特殊情况的其他答案.

PS 如果需要,我可以包含用于获取行号的代码,尽管我确信它远非最佳。 (它涉及使用正则表达式查找时间戳的行号,然后剥离匹配的文本并从每行中减去 2 以获得页面标题)

【问题讨论】:

  • 所以你希望第 1-6 行作为一个文件,第 7-13 行作为另一个文件,第 14 行作为第三个文件?或者你想要一些动态的东西,在你的脚本前面提供了用于剪切文件的行号?
  • 您链接的答案正是您正在寻找的。您只需将<(printf '%s\n' $vec) 替换为您保存行号的文件,例如awk '...' lines.txt log.txt
  • @oguzismail 感谢您的解释,我不知道<(printf ...) 语句在做什么,但我现在看到我的行文件应该能够插入。但是当我尝试使用与该替换相同的解决方案它没有输出或错误,并且不写入任何新文件。
  • 这里工作正常。我将714 分别放入名为lines.txt 的文件中,并将示例输入从您的问题复制到名为log.txt 的文件中。然后我运行awk '...' lines.txt log.txt,它创建了log.txt.1log.txt.2log.txt.3。你做了什么与我描述的不同的事情吗?

标签: bash macos split onenote


【解决方案1】:

Bash 和 awk 解决方案

# Assumption: You have a bash array named arr with the indices you want,
# like this
arr=( 1 7 14 )

counter=1

for ((i=0; i<${#arr[@]}-1; i++)); do
    # Get current index
    index="${arr[$i]}"
    # Get next index
    next_index="${arr[$i+1]}"

    awk "NR>=$index && NR<$next_index" file_to_chop.txt > "log${counter}.txt"

    (( counter++ ))
done

# If the array is non-empty, we also need to write last set of lines
# to the last file
[ "${#arr[@]}" -gt 1 ] && {
    # Get last element in the array
    index="${arr[${#arr[@]}-1]}"

    awk "NR>=$index" file_to_chop.txt > "log${counter}.txt"
}

此脚本不适用于严格遵守 POSIX 的 shell,因为它使用了多个“bashism”,包括 (()) 中的算术。

这主要是通过使用 awk 的NR 来实现的,它给出了记录号。表达式

NR>=3

例如,告诉 awk 只对记录号大于或等于 3 的记录(或在我们的例子中为打印)执行操作。可以生成涉及 NR 的更复杂的布尔表达式例如,使用&amp;&amp;

NR>=3 && NR<=7

如果您还没有 bash 数组中的索引,您可以从如下文件生成数组:

arr=()
while read -r line; do arr+=( "$line" ); done < /path/to/your/file/here

或者如果你想从命令的输出中生成数组:

arr=()
while read -r line; do arr+=( "$line" ); done < <(your_command_here)

Python 解决方案

import sys


def write_lines(filename, lines):
    try:
        with open(filename, 'w') as f:
            f.write('\n'.join(lines))
    except OSError:
        print(f'Error: failed to write to "{filename}".', file=sys.stderr)
        exit(1)


if len(sys.argv) != 2:
    print('Must pass path to input file.', file=sys.stderr)
    exit(1)

input_file = sys.argv[1]
line_indices = [line.rstrip() for line in sys.stdin]

try:
    with open(input_file, 'r') as f:
        input_lines = [line.rstrip() for line in f]
except OSError:
    print(f'Error: failed to read from "{input_file}".', file=sys.stderr)
    exit(1)

counter = 1

while len(line_indices) > 1:
    index = int(line_indices.pop(0))
    next_index = int(line_indices[0])

    write_lines(f'log{counter}.txt', input_lines[index-1:next_index-1])

    counter += 1

if line_indices:
    index = int(line_indices[0])

    write_lines(f'log{counter}.txt', input_lines[index-1:])

这是用法,假设你想剪切一个文件,所以第 1-6 行输出到log1.txt,第 7-13 行输出到log2.txt,第 14 行及以后输出到log3.txt:

printf '1\n7\n14\n' | python chop_file_script.py /path/to/file/to/chop

其操作方式是通过阅读stdin 来了解如何将输入文件分割成单独的文件。这是设计使然,因此可以使用管道将所需的行号从父 shell 脚本提供给脚本(如上面的使用示例)。

这不是一个完全健壮的脚本。它不处理类似的事情,例如:

  • stdin 中的行号未按升序排列
  • stdin 包含非数字值
  • stdin 中的数字超过了输入文件的长度

我相信这个脚本不是完全健壮的很好,因为只要以预期的方式使用它就应该正常工作。

【讨论】:

    【解决方案2】:

    请你试试bashsed的组合:

    #!/bin/bash
    
    mapfile -t lines < "lines.txt"                  # read "lines" file and assign array "lines"
    for (( i = 0; i < ${#lines[@]}; i++ )); do      # loop over the array "lines"
        start=${lines[i]}                           # start line
        if (( i == ${#lines[@]} - 1 )); then        # for the last element
            end="$"                                 # end line = "$"
        else                                        # otherwise
            end=$(( ${lines[i+1]} - 1 ))            # end line = next start line - 1
        fi
        sed -n "${start},${end}p" "log.txt" > "log$(( i + 1 )).txt"
                                                    # extract the lines and write into a separate file
    done
    

    【讨论】:

      猜你喜欢
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-23
      • 1970-01-01
      • 2021-07-03
      • 1970-01-01
      相关资源
      最近更新 更多