如果一行仅包含一个字符,如何删除行


10

我只想从包含特定字符的文件中删除一行,如果该行存在多次或不存在,则将该行保留在文件中。

例如:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

在这里,我要删除的字符是C这样,命令应该删除行FGTHDCJUTDYC因为它们C恰好有一次。

如何使用sed或来做到这一点awk

Answers:


20

在中,awk您可以将字段分隔符设置为任何值。如果将其设置为C,则字段+1的数量将为的数量C

所以,如果你说awk -F'C' '{print NF}' <<< "C1C2C3"4CCC由3 C秒,因此4个领域。

您要删除仅C出现一次的行。考虑到这一点,在您的情况下,您将要删除恰好有两个- C字段的那些行。因此,只需跳过它们:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD

4
巧妙使用awk场分隔符!
Valentin B.

中断,如在默认情况下(FS =“”)一样,它将忽略前导空格($ 1 =行中的第一个非空格)和重复项(您可以有5个空格来分隔字段1和字段2)...空格大概被特殊对待?(看到它,一个人可以做,awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'并给它喂一些线,有些具有多个spces,而另一些则以空格开头)
Olivier Dulac

2
@OlivierDulac,是的,空间是按POSIX的规定专门处理的。
通配符

8

sed方法:

sed -i '/^[^C]*C[^C]*$/d' input

-i 选项允许就地文件修改

/^[^C]*C[^C]*$/-匹配C仅包含一次的行

d -删除匹配的行


8

这可以通过以下方式完成sed

码:

sed '/C.*C/p;/C/d' file1

结果:

DTHGTY
HYTRHD
HTCCYD

怎么样?

  1. 匹配并打印至少具有Cvia的两个副本的任何行/C.*C/p
  2. Cvia 删除任何行/C/d,这包括在步骤1中已经打印的行
  3. 默认打印其余行

2
聪明的替代方法;我喜欢。
通配符

6

这将删除仅出现一次C的行。

grep -v '^[^C]*C[^C]*$' file

正则表达式[^C]匹配一个非C(或换行符)的字符,重复运算符(又名Kleene star)*指定零个或多个前一个表达式的重复。

grep(和大多数其他面向文本的工具)的默认输出为标准输出;重定向到新文件,如果需要的话,可以将其移动到原始文件的顶部。相同的正则表达式可用于sed -i就地编辑:

sed -i '/^[^C]*C[^C]*$/d' file

(在某些平台上,特别是* BSD包括macOS,该-i选项需要一个参数,例如-i ''。)


1
sed -i '/^[^C]*C[^C]*$/d' file-听起来好像是以前发布的,您怎么看,窃?
RomanPerekhrest

1
确实,有一些重复。我从grep答案开始,但显然很容易扩展到该sed -i变体。没有找到您的答案,因为我一直在寻找以前的grep答案。
tripleee

1
如果实用程序退出时没有错误,则明确地避免-i使用sed,而是重定向到一个新文件,并用该文件替换原始文件,这样比较安全sed
库萨兰达

2
或者grep -vx '[^C]*C[^C]*'
斯特凡Chazelas

@Kusalananda但是,您最好还是使用grep它,因为它更清晰,更健壮(尤其是sed信息量少的退出代码)。
三人房

4

用于脚本编辑文件(而不是将修改后的内容打印到标准输出中)的POSIX工具为ex

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

当然,如果您的Sed版本支持该功能,则可以使用sed -i,但要注意,如果要编写旨在在不同类型的系统上运行的脚本,则该功能是不可移植的。


大卫·佛斯特(David Foerster)在评论中问:

是否有一个原因,您正在使用printf,而不是echo或类似的东西ex -c COMMAND

答:可以。

对于printfvs. echo这是可移植性的问题;参见为什么printf比echo好? 而且,使用可以在命令之间插入换行符也更容易printf

对于printf ... | exvs. ex -c ...,这是一个错误处理问题。对于此特定命令,这无关紧要,但总的来说,它会起作用。例如,尝试将

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

在脚本中。与以下内容进行对比:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

第一个将挂起并等待输入;当ex命令接收到EOF时,第二个将退出,因此脚本将继续。还有其他解决方法,例如s///e,但是POSIX未指定。我更喜欢使用上面显示的可移植表格。

对于g命令,最后必须有一个换行符,并且我更喜欢使用printf换行符而不是将换行符嵌入单引号中。


1
是否有一个原因,您正在使用printf,而不是echo或类似的东西ex -c COMMAND
大卫·佛斯特

@DavidFoerster,是的。我开始用评论来回答您,但是它变得很长,因此我将其添加到了答案中。
通配符

谢谢,+ 1!我知道printfvs. echo(尽管通常我只喜欢echo在参数经过硬编码的情况下使用),但ex到目前为止我还没有广泛使用。
David Foerster'5

2

这是使用perl的几个选项。

由于您只匹配一个字符,因此您可以使用tr/C//(翻译,不能替换)返回的匹配数C

perl -lne 'print if tr/C// != 1' file

通常,如果要匹配多字符字符串或正则表达式,则可以使用以下命令:

perl -lne 'print if (@m = /C/g) != 1' file

这会将正则表达式的匹配项分配/C/g给一个列表,@m并且当该列表的长度不是时打印行1

-i可以添加该开关以编辑“就地”。


2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

需要注意的是它假定GNU sedt #...通常会转移到所谓的标签#...在大多数其他sed的实现。
斯特凡Chazelas

甚至!b是GNU sed,因为分支除了标签或换行符外什么都不喜欢。

是的,bt:}(和r filew file...)不能在他们之后的指令在同一行。您也可以使用单独的-e选项。
斯特凡Chazelas

您的perl选项无法产生正确的输出。我猜您忘了添加g修饰符。
汤姆·费内奇

@TomFenech你是正确的。我正在解决。谢谢。

1

对于任何想要的人awk,我会提供

awk '/C[^C]*C/{next}//{print}'

如果与模式匹配,请跳过该行,否则将其打印。您实际上并不需要{print},您可以使用//默认打印,但我认为它更清晰。

我的第一个想法是使用egrep -v相同的模式,但实际上并没有回答所提出的问题。


1
之后匹配什么有什么意义{next}?只需说一遍awk '/pattern/ {next} 1',所有与模式不匹配的行都将被打印。或者,最好awk '!/pattern/'直接打印这些。
fedorqui

@fedorqui有个很好的观点!/pattern/(某种程度上让我无视了),但是我宁愿看到一个不言自明的说法,而//{print}不是一个神秘的事物1。假设下一个人维护您的代码的能力和流利程度最低,这与不使它的效率或效率严重降低保持一致。
nigel222 '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.