如何编写sed脚本从文本文件中提取信息

6

我将尝试完成只能使用sed筛选输入文件以达成特定输出格式的作业。以下是输入文件(名为stocks):

Symbol;Name;Volume
================================================

BAC;Bank of America Corporation Com;238,059,612
CSCO;Cisco Systems, Inc.;28,159,455
INTC;Intel Corporation;22,501,784
MSFT;Microsoft Corporation;23,363,118
VZ;Verizon Communications Inc. Com;5,744,385
KO;Coca-Cola Company (The) Common;3,752,569
MMM;3M Company Common Stock;1,660,453

================================================

并且输出需要是:

BAC, CSCO, INTC, MSFT, VZ, KO, MMM

我想到了一个解决方案,但它不够高效。这是我的 sed 脚本(命名为 try.sed):

/.*;.*;[0-9].*/ { N
N
N
N
N
N
s/\(.*\);.*;.*\n\(.*\);.*;.*\n\(.*\);.*;.*\n\(.*\);.*;.*\n\(.*\);.*;.*\n\(.*\);.*;.*\n\(.*\);.*;.*/\1, \2, \3, \4, \5, \6, \7/gp
}

我在终端上运行的命令是:

$ sed -nf try.sed stocks

我的问题是,有没有更好的方法使用sed来获得相同的结果?我编写的脚本只适用于7行数据。如果数据更长,我需要重新修改我的脚本。我不确定如何让它更好,所以我在这里寻求帮助!感谢任何建议。

5
+1 是因为你承认这是一份作业,而且那个野生的 s/\(.*\);....../ 的东西真的很厉害!祝你好运。 - shellter
4个回答

2
编辑:我已经修改了我的算法,因为我忽略了标题和页脚(我以为它们只是为我们自己看的)。 sed 的设计是访问输入文件的每一行,然后对符合某些规范(或没有规范)的行执行表达式。如果你正在调整脚本以适应某个特定数量的行,那么你肯定做错了什么!由于这是作业,我不会为你编写脚本,但一个实现的一般思路是编写一个脚本,按照以下顺序执行以下操作。将以下内容视为脚本中应该有的顺序。
  1. 使用 d 跳过前三行,它删除模式空间并立即移到下一行。
  2. 对于每行不为空的行,请执行以下步骤。 (这将全部在一组花括号中完成。)
    1. 使用 s 命令,用逗号和空格(", ")替换第一个分号(;)及其之后的所有内容。
    2. 将当前模式空间附加到保留缓冲区中(查看 H)。
    3. 删除模式空间并移动到下一行,就像步骤 1 中那样。
  3. 对于此脚本中到达此点的每行(应该是第一个空行),将保留缓冲区的内容检索到模式空间中。(这将在上面的花括号之后。)
  4. 用空白替换模式空间中的所有换行符。
  5. 接下来,在模式空间中用空白替换最后一个逗号和空格。
  6. 最后,退出程序,以便不再处理任何行。我的脚本没有这个也能运行,但我不确定为什么。

话虽如此,这只是一种方法。sed 经常提供各种各样的复杂度不同的方法来完成任务。我使用这种方法编写的解决方案有 10 行长。

值得注意的是,我不费力地抑制打印(使用 -n)或手动打印(使用 p);每行默认都会打印。我的脚本运行如下:

$ sed -f companies.sed companies 
BAC, CSCO, INTC, MSFT, VZ, KO, MMM

@Jaycee,你对上面的哪一部分有困难?如果可以的话,我想改进我的解释! - Dan Fego
嗨,丹,谢谢你的提示。对于第一步,我会用逗号和空格获取所有符号。但是我在做第二步时遇到了麻烦。如何获取除最后一行以外的每一行?从技术上讲,MMM不是最后一行。 ============ 是最后一行。我很困惑,真的不知道该怎么办。你能否再详细解释一下?非常感谢你的帮助! - Jaycee
我可以按照以下方式获取最后一个:/[0-9]$/ { N N s/(.);.;.\n\n=/\1/gp } - Jaycee
@Jaycee,对于我的测试程序,我已经去掉了标题和页脚,因为我认为它们只是说明性的,不是实际输入的一部分。它们使脚本变得有点复杂。我已经更新了上面的内容,但它并不美观! - Dan Fego
嗨,丹,非常感谢你的所有帮助和建议!!!我现在终于知道如何使用 H 和 g 了。我不知道从保留空间获取副本后会有一个 \n。而且这个 \n 字符不能被替换为删除空行。(我以为它像一个空行一样工作。) 这是我的代码(在 Birei 的帮助下): 1,/^$/d /;/ { s/\(.*\)\(;.*;.*\)/\1/ H d } $ { g s/\n// s/\n/, /g p }但是对于 'd' 命令,我们需要将其放在那里吗?我从脚本中删除了它,似乎也可以正常工作。非常感谢您的时间和知识!!我深表感激!!!! ^_^ - Jaycee
显示剩余4条评论

2

使用 sed 的另一种方法:

sed -ne '/^====/,/^====/ { /;/ { s/;.*$// ; H } }; $ { g ; s/\n// ; s/\n/, /g ; p }' stocks

输出:

BAC, CSCO, INTC, MSFT, VZ, KO, MMM

解释:

-ne               # Process each input line without printing and execute next commands...
/^====/,/^====/   # For all lines between these...
{
  /;/             # If line has a semicolon...
  { 
    s/;.*$//      # Remove characters from first semicolon until end of line.
    H             # Append content to 'hold space'.
  }
};
$                 # In last input line...
{
  g               # Copy content of 'hold space' to 'pattern space' to work with it.
  s/\n//          # Remove first newline character.
  s/\n/, /g       # substitute the rest with output separator, comma in this case.
  p               # Print to output.

哇,谢谢Birei!我不知道我可以使用双{},而且我忘记了我可以只使用substitute命令而不是g命令来匹配第一个出现的匹配项。我还有几个问题:
  1. 为什么最后一个块在最后一行模式($)上?
  2. 对于第二个换行符的替换,它的目的是删除空行吗?
  3. 对于最后一个换行符的替换,为什么它没有替换“MMM”后面的换行符?
你给了我很好的解释,但我仍然不理解$ {}的目的。希望你能帮助我更好地理解它。非常感谢你的帮助!
- Jaycee
@Jaycee:[1] 我在文件处理过程中将所需字符串保存在“保留空间”中,仅在最后一行恢复该内容,进行修改并打印。[2] H 命令将 \n 和“模式空间”的内容附加到“保留空间”,因此在最后一行,内容将类似于 \nBAC\nCSCO\nINTC\nMSFT\nVZ\nKO\nMMM。然后我删除第一个 \n 并用 , 替换其余的。 - Birei
啊哈……我明白了!!!非常感谢!!!!使用H和g很酷……=)不确定为什么我的老师没有教我们这些命令。再次感谢!!!!^O^ - Jaycee

0

这个可能适合你:

sed '1d;/;/{s/;.*//;H};${g;s/.//;s/\n/, /g;q};d' stocks
  • 我们不想要标题,所以让我们删除它们。1d
  • 所有数据项都由;分隔,所以让我们集中处理这些行。/;/
  • 在上述内容中,从第一个;到行末的所有内容都将被删除,然后将其存储在保留空间(HS)中。{s/;.*//;H}
  • 当您到达最后一行时,使用g命令用HS覆盖它,删除第一个换行符(由H命令生成),将所有后续换行符替换为逗号和空格,并打印剩余的内容。${g;s/.//;s/\n/, /g;q}
  • 删除其他所有内容。d

以下是一个终端会话,显示了构建sed命令的逐步改进:

cat <<! >stock # paste the file into a here doc and pass it on to a file
> Symbol;Name;Volume
> ================================================
> 
> BAC;Bank of America Corporation Com;238,059,612
> CSCO;Cisco Systems, Inc.;28,159,455
> INTC;Intel Corporation;22,501,784
> MSFT;Microsoft Corporation;23,363,118
> VZ;Verizon Communications Inc. Com;5,744,385
> KO;Coca-Cola Company (The) Common;3,752,569
> MMM;3M Company Common Stock;1,660,453
> 
> ================================================
> !
sed '1d;/;/!d' stock # delete headings and everything but data lines
BAC;Bank of America Corporation Com;238,059,612
CSCO;Cisco Systems, Inc.;28,159,455
INTC;Intel Corporation;22,501,784
MSFT;Microsoft Corporation;23,363,118
VZ;Verizon Communications Inc. Com;5,744,385
KO;Coca-Cola Company (The) Common;3,752,569
MMM;3M Company Common Stock;1,660,453
sed '1d;/;/{s/;.*//p};d' stock # delete all non essential data
BAC
CSCO
INTC
MSFT
VZ
KO
MMM
sed '1d;/;/{s/;.*//;H};${g;l};d' stock # use the l command to see what's really there!
\nBAC\nCSCO\nINTC\nMSFT\nVZ\nKO\nMMM$
sed '1d;/;/{s/;.*//;H};${g;s/.//;s/\n/, /g;l};d' stock # refine refine
BAC, CSCO, INTC, MSFT, VZ, KO, MMM$
sed '1d;/;/{s/;.*//;H};${g;s/.//;s/\n/, /g;q};d' stock # all done!
BAC, CSCO, INTC, MSFT, VZ, KO, MMM

0

这个sed命令应该能够生成您需要的输出:

sed -rn '/[0-9]+$/{s/^([^;]*).*$/\1/p;}' file.txt

或者在 Mac 上:

sed -En '/[0-9]+$/{s/^([^;]*).*$/\1/p;}' file.txt

亲爱的anubhava,我已经运行了您的命令,但输出不在一行中。其中一个挑战是将所有换行符替换为逗号和1个空格,除了最后一行。最后一个后面不应该有逗号。 - Jaycee
是的,我的脚本的行为与grep -o完全相同,因为现在我意识到这是一项作业,所以我将把剩下的脚本交给你。 - anubhava
@Jaycee,另一个提示是在标签的使用中(如@Dan所建议的那样),使用N和将“newlines”替换为,空格 - jaypal singh
感谢Jaypal和Anubhava...我正在尝试更多地了解关于b和t命令的标签。虽然,我还不太明白t在做什么...... - Jaycee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接