我有一个包含数千行的示例文件。 我想在该文件中的两个行号之间打印文本。我不想手动输入行号,而是我有一个文件,其中包含必须打印文本的行号列表。
示例:linenumbers.txt
345|789
999|1056
1522|1366
3523|3562
我需要一个shell脚本,它将读取此文件中的行号,并将每行范围内的文本打印到一个单独的(新)文件中。
也就是说,它应该将345到789之间的行打印到一个新文件中,比如File1.txt
,然后将行999和1056之间的文本打印到一个新文件中,比如说File2.txt
,依此类推。
答案 0 :(得分:2)
考虑到你的目标文件只有几千行。这是一个快速而肮脏的解决方案。
awk -F'|' '{system("sed -n \""$1","$2"p\" targetFile > file"NR)}' linenumbers.txt
targetFile
是包含数千行的文件。linenumbers.txt
进行排序。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