我正在尝试预处理发票文件并将每张发票分成单独的文件。发票可能是多页的。每页的标题如下所示:
121084
A134
09.17.19
1
每页顶部有 6 个空行,后面是发票编号、客户编号、日期和页码(然后是发票的其余部分)。
在每页第 10 行(每页第 10 行是发票页号)的每个“1”(70 个空格后跟“1”)处,我需要在第 1 行插入一些文本(将用作分隔符)分割文件)。发票可以有多页,但“1”(70 个空格后跟“1”)表示这是一张新发票。
当遇到“ 1”(70个空格后跟“1”)时,我想在其上方9行的空白行(这是此发票的第一行)插入一些文本。对文件中的每个出现位置都执行此操作。然后我可以将每个发票的文件分成一个单独的文件。
我知道我可以使用 sed 在模式之前立即插入数据,但是如何在其上方 9 行插入数据?
我通常可以用 sed 和 awk 完成它,但这一个难倒了我。
答案1
假设你想说的是:
我有一个很长的文件,里面有几张发票。每张发票均以文本开头70 spaces and a 1
。我需要在每张发票的内容之前插入新内容 9 行
然后你需要做的就是积累10行。当当前行与发票开始在第一行插入一些新文本。
在实践中,这可以实现:
sed -e '1{N;N;N;N;N;N;N;N}' -e 'N;l;/\n \{70\}1$/{iNew content here' -e 'P};P;D' file
或者,以长形式(注释不适用于某些 sed 实现):
sed '1{ # (only) on the first line
N;N;N;N;N;N;N;N # accumulate 9 lines (first one plus 8 more).
}
N # On every line,also accumulate that line
/\n \{70\}1$/{ # If buffer ends ($) in 70 spaces and a "1"
i\
New content added here # insert the content of this line at
# the start of the buffer (10 lines above).
P # and then print it.
}
P;D # close the N cycle above by
# printing and deleting one line
' file # On the selected file.
解决awk
方案可以利用将 RS 设置为标记发票开始的行。在这种情况下,将 FS 设置为换行符会将每一行分成一个字段,并且 $(NF-9) 将引用上面的第 9 行:
awk -v ln=9 '
( NF < ln ){ print; next }; # not enought fields?
# include
{ $(NF-ln) = "New Text to include" newln $(NF-ln);
print # and print
};
BEGIN{ breakln = sprintf("%71s",1); # 70 spaces and a 1
newln = sprintf("\n"); # a newline
RS = breakln newln; # set the Record Separator
FS = "\n"; # set the Field separator
OFS = FS; # print what got removed
ORS = RS # print what got removed
}
' file
或者,(shell/awk 解决方案):
breakln="$(printf '%71s' 1)";
newl=$'\n';
awk ' (NF<ln){print;next};
{ $(NF-ln)="New Text to include\n"$(NF-ln); print}' \
ln=10 \
RS="$breakln$newl" \
FS="$newl" \
OFS="$newl" \
ORS="$breakln$mewl" \
file
答案2
与awk
+ tac
:
tac file | awk -v delim="--split page here--" '{
if (nextnr=="" && $1~/^[0-9]+$/ && $0==" "$1) {
nextnr=NR+9 # pagenr found, remember next position
}
else if (NR==nextnr) {
$0=delim # overwrite line with delimiter
nextnr="" # reset
}
print
}' | tac
首先使用 反转文件tac
,这样我们就可以自上而下搜索页码并插入分隔符。
当满足这些条件时,搜索从 if 子句开始:
nextnr
(最初)未设置 (nextnr==""
)。这是保存下一个分隔符的行号的变量。- 第一个字段是数字 (
$1~/^[0-9]+$/
) - 该行包含 70 个空格和数字
如果三个条件全部成立,则nextnr
设置为当前行号(记录数)NR + 9
。
如果当前行是分隔符 ( ) 的行NR==nextnr
,则用分隔符覆盖该行并重置nextnr
。
脚本的最后一行打印当前行(原始行或被分隔符覆盖)。
在最后一步中,输出再次用 反转tac
。
答案3
这是一个可编写脚本的编辑器解决方案。其想法是仅根据文件中“(70 个空格)1”行的数量来找出文件中有多少张发票。然后该脚本会循环多次并输出 的命令ed
。该循环输出足够的命令来更改“带有‘(70 个空格) 1’的行之前的第 9 行”,以改为虚线剪切线。将-----8<-----
文本替换为除了裸露的前导句点(ed
用于区分替换字符串的结尾)之外的任何内容。如果我们正在循环查看发票 ( i < count
),请在进行更改后向前跳 10 行,这样我们就不会不会重新发现我们刚刚截取的页面。如果我们完成了循环 ( i == count
),则打印出 ed 的“write and quit”命令,所有 printf/echo 输出都会发送到管道,该选项ed
将作为输入读取。-s
“静默”模式——ed
不会报告读取或写入的字节数。
#!/usr/bin/bash
count=$(grep -c '^ 1' input)
for((i=1; i<=count; i++))
do
printf '%s\n' '/^ 1/-9c'
printf '%s\n' '-----8<-----' '.'
[[ $i < $count ]] && printf '%s\n' '+10'
[[ $i == $count ]] && echo wq
done | ed -s input