![打印大写字母行中每个单词都以不同字母开头的行](https://linux22.com/image/197341/%E6%89%93%E5%8D%B0%E5%A4%A7%E5%86%99%E5%AD%97%E6%AF%8D%E8%A1%8C%E4%B8%AD%E6%AF%8F%E4%B8%AA%E5%8D%95%E8%AF%8D%E9%83%BD%E4%BB%A5%E4%B8%8D%E5%90%8C%E5%AD%97%E6%AF%8D%E5%BC%80%E5%A4%B4%E7%9A%84%E8%A1%8C.png)
我有这样的文字:
FOUR MILLION, EIGHT HUNDRED AND FIFTY-SEVEN THOUSAND, FIVE HUNDRED AND THIRTEEN innovating
FORTY-NINE MILLION, ONE HUNDRED AND EIGHTY THOUSAND, TWO HUNDRED AND FORTY-EIGHT championed
FORTY-SEVEN MILLION, NINE HUNDRED AND FIFTY-TWO THOUSAND, EIGHT HUNDRED AND SIX swashbuckling
NINE HUNDRED AND SIXTY-ONE THOUSAND, SIX HUNDRED AND THIRTY-ONE sprinklers
FORTY-TWO MILLION, TWO HUNDRED AND SIXTY-SIX THOUSAND, THREE HUNDRED AND SEVENTY-TWO furloughs
SEVEN MILLION, FOUR HUNDRED AND SEVENTEEN THOUSAND, FOUR HUNDRED AND FORTY-TWO panicky
THREE HUNDRED AND SEVENTY-NINE THOUSAND, FIVE HUNDRED AND TWENTY-EIGHT anchovies
FIVE MILLION, EIGHT HUNDRED AND FIFTY-NINE THOUSAND, FOUR HUNDRED AND SIXTY-FOUR excesses
............
如何使用grep
或sed
打印大写字母行中每个单词都以不同字母开头的行?
例如:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
答案1
解决此类问题时的首要任务是为工作选择正确的工具。在这个问题中,我们需要计算每个单词的首字母在一行中出现的次数。众所周知grep
, 和sed
都不擅长计数,至少它们本身不擅长,而 和awk
则更像是一种通用编程语言。如果我们想使用任何单一工具来解决任务,awk
可能会更适合。
awk '{
delete count
for (i = 1; i <= NF; ++i) {
ch = substr($i,1,1)
if (ch == toupper(ch) && count[ch]++)
next
}
print
}' file
该代码计算每行上所有单词的首字母大写字母的出现次数(单词是由空格分隔的子字符串)。我们将计数保存在关联数组中count
,并按数据中的字母进行索引。
当我们第二次遇到其中一个首字母时,我们会立即丢弃该行。我们以这种方式打印我们不会丢弃的每一行。
这段代码只关心这个词是否第一的字符为大写。要测试全大写单词的第一个字符,请使用以下命令:
awk '{
delete count
for (i = 1; i <= NF; ++i)
if ($i != toupper($i) && count[substr($i,1,1)]++)
next
print
}' file
下一个问题是理解代码。你已经得到现在的代码,它可以工作,但你可能不知道为什么。更重要的是,您可能不知道如何修改它以执行稍微不同的操作,或者如果它在您发现的某些边缘情况下突然失败,如何纠正它。
awk
您可以通过查找手册中的每一部分作为开始来更好地了解代码。然后,当你不明白为什么我写delete count
在那个特定的地方而不是其他地方时,你可以问另一个关于这个问题的问题,或者更好的是,尝试一下代码并注意它在哪些具体方面被破坏。
答案2
您可以使用正则表达式来扫描输入并获得所需的输出。
我们告诉grep
我们要查找一个大写单词,其第一个字符在该行中找到,但仅在另一个大写单词的开头。由于这意味着至少一个这样的匹配,但我们不希望这样的匹配,因此我们反转-v
匹配的含义以获得所需的输出。
编辑:根据@他们的观察,对其进行修改以查找大写单词。
grep -v '\<\([A-Z]\)[A-Z]\{1,\}\>.*\<\1[A-Z]\{1,\}\>' file
答案3
下面的 Perl 脚本过于冗长,可以大大缩短,但编写它是为了清楚地演示算法,而不是神秘地简洁:
$ cat caps.pl
#!/usr/bin/perl
use strict;
MAIN: while(<>) {
# skip lines without a capital letter
next unless /[A-Z]/;
# hash to hold the counts of the first letters of each word,
# reset to empty for every input line
my %letters = ();
foreach my $w (split /[-\s]+/) {
# ignore "words" not beginning with a letter
next unless $w =~ m/^[[:alpha:]]/;
# get the first character of the word
my $l = substr($w,0,1);
# uncomment if you want upper- and lower-case to be treated
# as the same letter:
#$l = uc($l);
$letters{$l}++;
# If we've seen this letter before on this line, skip to the
# next input line.
next MAIN if $letters{$l} > 1;
};
# the input line has no first letters which appear more than once, so print it.
print;
}
您的示例输入行都不会按照您给出的条件进行打印,因此我将两个示例输出行添加到输入中:
$ ./caps.pl input.txt
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGHT
答案4
使用 Raku(以前称为 Perl_6)
raku -ne '.put if .words.map(*.comb(/ ^<upper> /)).Bag.values.max == 1;'
输入示例:
FOUR MILLION, EIGHT HUNDRED AND FIFTY-SEVEN THOUSAND, FIVE HUNDRED AND THIRTEEN innovating
FORTY-NINE MILLION, ONE HUNDRED AND EIGHTY THOUSAND, TWO HUNDRED AND FORTY-EIGHT championed
FORTY-SEVEN MILLION, NINE HUNDRED AND FIFTY-TWO THOUSAND, EIGHT HUNDRED AND SIX swashbuckling
NINE HUNDRED AND SIXTY-ONE THOUSAND, SIX HUNDRED AND THIRTY-ONE sprinklers
FORTY-TWO MILLION, TWO HUNDRED AND SIXTY-SIX THOUSAND, THREE HUNDRED AND SEVENTY-TWO furloughs
SEVEN MILLION, FOUR HUNDRED AND SEVENTEEN THOUSAND, FOUR HUNDRED AND FORTY-TWO panicky
THREE HUNDRED AND SEVENTY-NINE THOUSAND, FIVE HUNDRED AND TWENTY-EIGHT anchovies
FIVE MILLION, EIGHT HUNDRED AND FIFTY-NINE THOUSAND, FOUR HUNDRED AND SIXTY-FOUR excesses
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
示例输出:
FIFTY THOUSAND, NINE HUNDRED AND EIGHTEEN
FOURTEEN THOUSAND, SEVEN HUNDRED AND NINETY-EIGH
使用 Raku 中的一行代码可以轻松解决这个问题,Raku 是以前称为 Perl6(于 2019 年重命名)的编程语言的新名称。
简而言之,使用命令行标志将输入逐行读入 Raku -ne
。输入被分解为以空格分隔的words
,检查每个单词(使用map
)并过滤(使用comb
)以查找以大写字母开头的单词(使用^<upper>
正则表达式)。然后对这些字母进行Bag
-ged,计算出现的次数,并且只max == 1
返回出现次数的行(即没有重复字母)。
对于这个问题的“词”的构成似乎有一些评论。如果要将连字符作为单独的单词进行计数,请首先通过添加.split("-")
到方法链的开头(在 之前.words
)来按连字符进行分割。
为了让您了解上面的 Raku 代码是如何工作的,以下是代码的核心和例行公事split
,但是没有有条件if
和无条件max
:
raku -ne '.split("-").words.map(*.comb(/ ^<upper> /)).Bag.put;'
H(2) M A(2) T(2) E S F(3)
T(2) N E(2) H(2) O F(2) M A(2)
M S(2) T(2) N A(2) E H(2) F(2)
O(2) H(2) S(2) A(2) T(2) N
M H(2) A(2) S(3) F T(5)
S(2) F(3) A(2) H(2) T(2) M
T(3) H(2) S E F N A(2)
H(2) T S M N A(2) F(4) E
A E F H N T
E T F N H S A
https://docs.raku.org/language/regexes#Predefined_character_classes
https://raku.org