【问题标题】:Using sed to go to a specific line, change pattern then print all between line and another pattern使用 sed 转到特定行,更改模式,然后在行和另一个模式之间打印所有内容
【发布时间】:2016-05-27 14:28:34
【问题描述】:

所以我需要用之前找到的一行来更改大文本文件中的特定行。文字是什么样子的:

名称:一些文字
Société: 一些文字
地址:一些文本和 numb3rs 代码 邮政编码:[0-9][0-9][0-9][0-9][0-9] SOME TEXT
电话。 :
数字
传真:
数字
“----------”

到目前为止我发现的是(我相信我快完成了):

K=0
while [ $K -lt 11519 ]; do
    let K=K+1
    L=`head -n $K file_that_contains_line_numbers_I_want.txt | tail -1`
    M=`expr $L - 2`
    dept=`head -n $L filename.txt | tail -1 | sed -e 's/Adresse:.*Code Postal: //' -e 's/[0-9]\{3\} .*//'`
    sed -n ""$M"{s/Tél. :/$dept/; /----------------------/p; q}" filename.txt >>newfile.csv
done

其中 $dept 是邮政编码后的前两位数字:.
sed 的最后一点还不起作用:我希望最终文件看起来像旧文件,只是带有“Tél”。部分更改为 $dept。
新文件:

名称:一些文字
Société: 一些文字
地址:一些文本和 numb3rs 代码邮政编码:90000 SOME TEXT
90
数字
传真:
数字
“----------”

显然这种模式与名称重复,但有时 Tél 行。及以下不存在。

tl博士;我想更改文件中的模式,其中找到的东西排成一行,而找到的东西排成一排。

如果您找到一种不同的方式将 $dept 放在不同的行中,我会很高兴听到这个消息。

我知道我的代码并不是最有效的,但我只在一周前了解了 sed。

提前感谢您帮助我/纠正我。

编辑:我被要求提供一些输入,这里是:

名称:JOHN DOE
Société: APERTURE SCIENCE
地址:37 RUE OF PARIS CS 30112 邮政编码:51726 REIMS CEDEX
电话。 :
12 34 56 78 90
传真:
12 34 56 78 90
“----------”
名称:OLIVER TWIST
Société: 美国国家航空航天局
地址:40 RUE DU GINGEMBRE CS 70999 邮政编码:67009 STRASBOURG CEDEX
电话。 :
12 34 56 78 90
传真:
12 34 56 78 90
“----------”
名称:巴拉克·奥巴马
Société: 白宫
地址:124 BOULEVARD DE GAULLE 代码邮政编码:75017 PARIS
电话。 : 12 34 56 78 90
“----------”

我想要实现的输出:

名称:JOHN DOE
Société: APERTURE SCIENCE
地址:37 RUE OF PARIS CS 30112 邮政编码:51726 REIMS CEDEX
51
12 34 56 78 90
传真:
12 34 56 78 90
“----------”
名称:OLIVER TWIST
Société: 美国国家航空航天局
地址:40 RUE DU GINGEMBRE CS 70999 邮政编码:67009 STRASBOURG CEDEX
67 12 34 56 78 90
传真:
12 34 56 78 90
“----------”
名称:巴拉克·奥巴马
Société: 白宫
地址:124 BOULEVARD DE GAULLE 代码邮政编码:75017 PARIS
75 12 34 56 78 90
“----------”

【问题讨论】:

  • 这个不是很清楚,你用什么替换什么?
  • 会更容易使用awk.
  • 11519 只是一个任意数字还是您实际上在寻找while read -r L; do ... done<file_that_contains_line_numbers_I_want.txt?
  • @tripleee ls 1159 不是任意数字,它对应于包含模式 Adresse: 的行数 -1:(我可以在其中找到我的变量 $dept)。
  • 不幸的是,您还没有完成,只是看起来是这样 :-)。请阅读why-is-using-a-shell-loop-to-process-text-considered-bad-practice 以了解为什么应该将您拥有的东西扔到很远的地方并从 awk 脚本重新开始的一些原因。不幸的是,我无法从您的问题中弄清楚您要做什么 - 请 edit 您的问题包括简洁、可测试的样本输入和预期输出。

标签: bash sed debian gnu


【解决方案1】:

使用 sed :

$ sed '/.*Code Postal: \([0-9][0-9]\).*/{p;s//\1/;n;d}' file
Nom: some text
Société: some text
Adresse: some text and numb3rs Code Postal: 90000 SOME TEXT
90
numbers
Fax :
numbers
"----------------------"
  • /.*Code Postal: \([0-9][0-9]\).*/ :搜索包含Code Postal: 后跟两位数字的行
  • p:打印匹配行(即克隆包含“邮政编码”的行)
  • s//\1/ :用捕获的数字 (\([0-9][0-9]\)) 替换匹配行 (s//\1)
  • n 读取下一行 ("Tél") 并将其删除 (d)

我刚刚看到您的编辑,您可以通过以下方式实现:

sed '/.*Code Postal: \([0-9][0-9]\).*/{p;s//\1/;N;/[0-9]/s/\n/ /;s/Tél\. : *//}' file

请注意,部门编号将在“OLIVER TWIST”块的单行输出(因为 Tél.: 与第一个块一样在单行)

【讨论】:

  • 非常感谢您的回答,它有效!如果您能抽出一些时间向我解释您到底做了什么,我将非常感激,特别是 {p;s//\1/;n;d} 部分。我相信它是这样开始的:打印;用出现次数 1 然后 n 代替任何内容?然后删除;但正如你所看到的,这只是一个猜测。
【解决方案2】:

您没有提供要检查的示例输入,但这应该可以:

/Code Postal:/ {
    match($0, /Code Postal: *([0-9][0-9])/, result);
    dept = result[1];
}

/^Tél/ { $2 = dept }

{ print }

将代码保存到文件中,然后调用awk -f file input_file。它的工作原理如下:如果该行匹配“邮政编码”,则将邮政编码的前两位数字保存在变量dept 中。如果该行以“Tél”开头,则将第二个字段替换为dept 的值。然后,打印任意行。

【讨论】:

  • 首先,感谢您的回答,但我非常希望:您能解释一下答案,以便我从中学习。另外,这是否必须在我的循环中替换 sed 或者我可以通过在你写的内容之前输入 awk 来使用它吗?
  • 这是一个 awk 脚本。您可以使用awk '/Code Postal:/ { ... #etc } { print }' filename 替换您当前的 bash 脚本。不过,我不确定它是否符合您的要求;如果您有两个文件,其中一个文件包含行号,则脚本需要稍有不同。也许您仍然可以澄清您的问题。
【解决方案3】:

这是我对您要完成的工作的猜测。

awk 'NR==FNR { # Store line numbers in a[]
    a[$1] = $1; next }
   FNR in a { m=1 } # We are in match range
   /^------$/ { m=0 }  # Separator: we are out of range
   m && /^Adresse.*Code postal:/ { c=substr($NF, 1, 2); $NF = 90000 }
   m && /^Tél\. :$/ { $0 = c }
   { print }' file_that_contains_line_numbers_I_want.txt filename > filename.new

这包含一些常见的 awk 习语。下面是一个非常简短的脚本草图。

NR 是当前行号,FNR 是当前文件中的文件号。当它们相等时,这意味着您正在读取第一个输入文件。在这种情况下,我们将行号读入数组a 并跳到下一行。

如果我们失败了,我们正在读取第二个文件。当我们看到a 中存在的行号时,我们将标志m 设置为真(非零)值,以指示我们处于应该进行替换的区域。当我们看到虚线时,我们将其清除,因为这标志着当前记录的结束。

最后,如果我们在目标记录之一中(m 为真),我们会查找模式并执行请求的提取和替换。 NF是当前行的字段数,$选择一个字段,所以$NF = 90000替换了该行的最后一个字段;而$0 是整个输入行,所以当我们看到Tél. : 时,我们将整行替换为提取的代码。

在脚本的最后,我们打印我们正在阅读的任何内容;第一个块中的next 跳过了脚本的其余部分,所以我们只在第二个文件中打印。结果输出应该(希望!)是您需要的结果。

这应该比一遍又一遍地读取同一个文件要快几个数量级,并且只要第一个文件包含的行号少于数百万(假设现代硬件;如果你有一台非常小的机器内存和没有交换,可能是数万)。

【讨论】:

  • 感谢您花时间提供答案并解释答案。我有点理解你的代码,除了 {c=substr($NF, 1, 2); $NF = 90000} 部分,因为我读到的关于 substr 的内容是: substr(string,position,length) 而我不知道 $NF; grymoire.com 还表示 awk 的字段限制为每行 99 个字段,所以 $NF = 90000 可能是错误的?如果您认为 90000 在文本中是常数,我很抱歉没有说清楚。
  • 是的,NF 是当前行的字段数,所以$NF 寻址最后一个字段——如果有六个字段,那么NF 是 6,$6 寻址第六场就行了。我们正在替换最后一个字段的值,而不是更改它的索引;所以我们用 90000 替换(比如)91234,并在替换之前将原始值的前两位保留在 c 中。
  • 显然您根本不想替换$NF,并且邮政编码不是最后一个字段,因此部分代码不适用于您当前形式的问题。
【解决方案4】:

听起来这可能是你想要的,使用 GNU awk 作为第三个参数来匹配()):

$ awk 'match($0,/.*Code Postal: *([0-9][0-9])/,a){$0=$0 ORS a[1]} !/^Tél/' file

或 gawk 或 mawk 用于 gensub():

$ awk '{$0=gensub(/.*Code Postal: *([0-9][0-9]).*/,"&\n\\1",1)} !/^Tél/' file
Nom: some text
Société: some text
Adresse: some text and numb3rs Code Postal: 90000 SOME TEXT
90
numbers
Fax :
numbers
"----------------------"

上面是在这个输入文件上运行的:

$ cat file
Nom: some text
Société: some text
Adresse: some text and numb3rs Code Postal: 90000 SOME TEXT
Tél. :
numbers
Fax :
numbers
"----------------------"

上面的内容与规定的正则表达式匹配,将捕获的 2 位数字保存在数组 a[1] 中,并在打印当前行和任何其他未开始的行之前将其前面的换行符 (ORS) 添加到当前行的末尾Tél。

如果您要在 UNIX 中进行任何文本操作,请阅读 Arnold Robbins 的《Effective Awk programming, 4th Edition》。

【讨论】:

  • 谢谢你的回答,它也有效!您能花点时间向我解释一下您的代码,以便我可以更好地学习使用 awk 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-03
  • 2019-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多