


Linux培训
达内IT学院
400-996-5531
今天小编要跟大家分享的文章是关于Linux系统中cut命令的4个实用示例,相信各位喜欢Linux系统的小伙伴们对cut命令并不陌生。cut 命令是用来从文本文件中移除“某些列”的经典工具。在本文中的“一列”可以被定义为按照一行中位置区分的一系列字符串或者字节,或者是以某个分隔符为间隔的某些域。今天小编分享的文章就是为大家解释linux下 cut 命令的4个本质且实用的例子,有时这些例子将帮你节省很多时间。
Linux下cut命令的4个实用示例
1、作用在一系列字符上
当启用 -c 命令行选项时,cut 命令将移除一系列字符。
和其他的过滤器类似, cut 命令不会直接改变输入的文件,它将复制已修改的数据到它的标准输出里去。你可以通过重定向命令的结果到一个文件中来保存修改后的结果,或者使用管道将结果送到另一个命令的输入中,这些都由你来负责。
假如你已经下载了上面视频中的示例测试文件,你将看到一个名为 BALANCE.txt 的数据文件,这些数据是直接从我妻子在她工作中使用的某款会计软件中导出的:
上述文件是一个固定宽度的文本文件,因为对于每一项数据,都使用了不定长的空格做填充,使得它看起来是一个对齐的列表。
这样一来,每一列数据开始和结束的位置都是一致的。从cut命令的字面意思去理解会给我们带来一个小陷阱:cut命令实际上需要你指出你想保留的数据范围,而不是你想移除的范围。所以,假如我只需要上面文件中的ACCOUNTNUM和ACCOUNTLIB列,我需要这么做:
范围如何定义?
正如我们上面看到的那样,cut命令需要我们特别指定需要保留的数据的范围。所以,下面我将更正式地介绍如何定义范围:对于cut命令来说,范围是由连字符(-)分隔的起始和结束位置组成,范围是基于1计数的,即每行的第一项是从1开始计数的,而不是从0开始。范围是一个闭区间,开始和结束位置都将包含在结果之中,正如它们之间的所有字符那样。如果范围中的结束位置比起始位置小,则这种表达式是错误的。作为快捷方式,你可以省略起始或结束值,正如下面的表格所示:
cut命令允许你通过逗号分隔多个范围,下面是一些示例:
#保留1到24之间(闭区间)的字符
cut -c -24 BALANCE.txt
#保留1到24(闭区间)以及36到59(闭区间)之间的字符
cut -c -24,36-59 BALANCE.txt
#保留1到24(闭区间)、36到59(闭区间)和93到该行末尾之间的字符
cut -c -24,36-59,93- BALANCE.txt
cut命令的一个限制(或者是特性,取决于你如何看待它)是它将 不会对数据进行重排。所以下面的命令和先前的命令将产生相同的结果,尽管范围的顺序做了改变:
cut -c 93-,-24,36-59 BALANCE.txt
你可以轻易地使用diff命令来验证:
diff -s <(cut -c -24,36-59,93- BALANCE.txt) \
<(cut -c 93-,-24,36-59 BALANCE.txt)
Files /dev/fd/63 and /dev/fd/62 are identical
类似的,cut命令 不会重复数据:
#某人或许期待这可以第一列三次,但并不会……
cut -c -10,-10,-10 BALANCE.txt | head -5
ACCDOC
4
4
4
5
值得提及的是,曾经有一个提议,建议使用-o选项来去除上面提到的两个限制,使得cut工具可以重排或者重复数据。但这个提议被POSIX委员会拒绝了,“因为这类增强不属于IEEE P1003.2b草案标准的范围”。
据我所知,我还没有见过哪个版本的cut程序实现了上面的提议,以此来作为扩展,假如你知道某些例外,请使用下面的评论框分享给大家!
2、作用在一系列字节上
当使用-b命令行选项时,cut命令将移除字节范围。
咋一看,使用字符范围和使用字节没有什么明显的不同:
sh$ diff -s <(cut -b -24,36-59,93- BALANCE.txt) \
<(cut -c -24,36-59,93- BALANCE.txt)
Files /dev/fd/63 and /dev/fd/62 are identical
这是因为我们的示例数据文件使用的是US-ASCII编码(字符集),使用file -i便可以正确地猜出来:
sh$ file -i BALANCE.txt
BALANCE.txt: text/plain; charset=us-ascii
在US-ASCII编码中,字符和字节是一一对应的。理论上,你只需要使用一个字节就可以表示256个不同的字符(数字、字母、标点符号和某些符号等)。实际上,你能表达的字符数比256要更少一些,因为字符编码中为某些特定值做了规定(例如32或65就是控制字符)。即便我们能够使用上述所有的字节范围,但对于存储种类繁多的人类手写符号来说,256是远远不够的。所以如今字符和字节间的一一对应更像是某种例外,并且几乎总是被无处不在的UTF-8多字节编码所取代。下面让我们看看如何来处理多字节编码的情形。
作用在多字节编码的字符上
正如我前面提到的那样,示例数据文件来源于我妻子使用的某款会计软件。最近好像她升级了那个软件,然后呢,导出的文本就完全不同了,你可以试试和上面的数据文件相比,找找它们之间的区别:
上面的标题栏或许能够帮助你找到什么被改变了,但无论你找到与否,现在让我们看看上面的更改过后的结果:
我毫无删减地复制了上面命令的输出。所以可以很明显地看出列对齐那里有些问题。
对此我的解释是原来的数据文件只包含US-ASCII编码的字符(符号、标点符号、数字和没有发音符号的拉丁字母)。
但假如你仔细地查看经软件升级后产生的文件,你可以看到新导出的数据文件保留了带发音符号的字母。例如现在合理地记录了名为 “ALNÉENRE” 的公司,而不是先前的 “ALNEENRE”(没有发音符号)。
file -i正确地识别出了改变,因为它报告道现在这个文件是UTF-8编码 的。
sh$ file -i BALANCE-V2.txt
BALANCE-V2.txt: text/plain; charset=utf-8
如果想看看UTF-8文件中那些带发音符号的字母是如何编码的,我们可以使用[hexdump][12],它可以让我们直接以字节形式查看文件:
在 hexdump 输出的00000030那行,在一系列的空格(字节 20)之后,你可以看到:
· 字母 A 被编码为 41,
· 字母 L 被编码为 4c,
· 字母 N 被编码为 4e。
但对于大写的带有注音的拉丁大写字母E (这是它在Unicode标准中字母 É 的官方名称),则是使用 2 个字节 c3 89 来编码的。
这样便出现问题了:对于使用固定宽度编码的文件, 使用字节位置来表示范围的 cut 命令工作良好,但这并不适用于使用变长编码的UTF-8或者 Shift JIS 编码。这种情况在下面的 POSIX标准的非规范性摘录 中被明确地解释过:
先前版本的 cut 程序将字节和字符视作等同的环境下运作(正如在某些实现下对退格键<backspace> 和制表键 <tab> 的处理)。在针对多字节字符的情况下,特别增加了 -b 选项。
嘿,等一下!我并没有在上面“有错误”的例子中使用 ‘-b’ 选项,而是 -c 选项呀!所以,难道不应该能够成功处理了吗!?
是的,确实应该:但是很不幸,即便我们现在已身处2018年,GNU Coreutils的版本为8.30了,cut 程序的GNU版本实现仍然不能很好地处理多字节字符。引用 GNU文档 的话说,-c 选项“现在和 -b 选项是相同的,但对于国际化的情形将有所不同[…]”。需要提及的是,这个问题距今已有10年之久了!
另一方面,OpenBSD 的实现版本和POSIX相吻合,这将归功于当前的本地化(locale)设定来合理地处理多字节字符:
正如期望的那样,当使用-b选项而不是-c选项后,OpenBSD版本的cut实现和传统的cut表现是类似的:
3、作用在域上
从某种意义上说,使用cut来处理用特定分隔符隔开的文本文件要更加容易一些,因为只需要确定好每行中域之间的分隔符,然后复制域的内容到输出就可以了,而不需要烦恼任何与编码相关的问题。
下面是一个用分隔符隔开的示例文本文件:
你可能知道上面文件是一个CSV格式的文件(它以逗号来分隔),即便有时候域分隔符不是逗号。例如分号(;)也常被用来作为分隔符,并且对于那些总使用逗号作为 十进制分隔符的国家(例如法国,所以上面我的示例文件中选用了他们国家的字符),当导出数据为 “CSV” 格式时,默认将使用分号来分隔数据。另一种常见的情况是使用tab键 来作为分隔符,从而生成叫做tab分隔的值 的文件。最后,在Unix和Linux领域,冒号(:)是另一种你能找到的常见分隔符号,例如在标准的/etc/passwd和/etc/group这两个文件里。
当处理使用分隔符隔开的文本文件格式时,你可以向带有-f选项的cut命令提供需要保留的域的范围,并且你也可以使用-d选项来指定分隔符(当没有使用-d选项时,默认以tab字符来作为分隔符):
处理不包含分隔符的行
但要是输入文件中的某些行没有分隔符又该怎么办呢?很容易地认为可以将这样的行视为只包含第一个域。但cut程序并 不是 这样做的。
默认情况下,当使用-f选项时,cut将总是原样输出不包含分隔符的那一行(可能假设它是非数据行,就像表头或注释等):
sh$ (echo "# 2018-03 BALANCE"; cat BALANCE.csv) > BALANCE-WITH-HEADER.csv
sh$ cut -f 6,7 -d';' BALANCE-WITH-HEADER.csv | head -5
# 2018-03 BALANCE
DEBIT;CREDIT
00000001615,00;
00000000323,00;
;00000001938,00
使用-s选项,你可以做出相反的行为,这样cut将总是忽略这些行:
sh$ cut -s -f 6,7 -d';' BALANCE-WITH-HEADER.csv | head -5
DEBIT;CREDIT
00000001615,00;
00000000323,00;
;00000001938,00
00000001333,00;
假如你好奇心强,你还可以探索这种特性,来作为一种相对隐晦的方式去保留那些只包含给定字符的行:
#保留含有一个`e`的行
sh$ printf "%s\n" {mighty,bold,great}-{condor,monkey,bear} | cut -s -f 1- -d'e'
改变输出的分隔符
作为一种扩展,GNU版本实现的cut允许通过使用--output-delimiter选项来为结果指定一个不同的域分隔符:
需要注意的是,在上面这个例子中,所有出现域分隔符的地方都被替换掉了,而不仅仅是那些在命令行中指定的作为域范围边界的分隔符。
4、非POSIX GNU扩展
说到非POSIX GNU扩展,它们中的某些特别有用。特别需要提及的是下面的扩展也同样对字节、字符或者域范围工作良好(相对于当前的GNU实现来说)。
--complement:
想想在sed地址中的感叹符号(!),使用它,cut将只保存没有被匹配到的范围:
--zero-terminated (-z):
使用NUL字符 来作为行终止符,而不是 新行newline字符。当你的数据包含 新行字符时,-z选项就特别有用了,例如当处理文件名的时候(因为在文件名中新行字符是可以使用的,而NUL则不可以)。
为了展示-z选项,让我们先做一点实验。首先,我们将创建一个文件名中包含换行符的文件:
bash$ touch $'EMPTY\nFILE\nWITH FUNKY\nNAME'.txt
bash$ ls -1 *.txt
BALANCE.txt
BALANCE-V2.txt
EMPTY?FILE?WITH FUNKY?NAME.txt
现在假设我想展示每个*.txt文件的前5个字符。一个想当然的解决方法将会失败:
sh$ ls -1 *.txt | cut -c 1-5
BALAN
BALAN
EMPTY
FILE
WITH
NAME.
你可能已经知道ls是为了方便人类使用而特别设计的,并且在一个命令管道中使用它是一个反模式(确实是这样的)。所以让我们用find来替换它:
sh$ find . -name '*.txt' -printf "%f\n" | cut -c 1-5
BALAN
EMPTY
FILE
WITH
NAME.
BALAN
上面的命令基本上产生了与先前类似的结果(尽管以不同的次序,因为ls会隐式地对文件名做排序,而find则不会)。
在上面的两个例子中,都有一个相同的问题,cut命令不能区分 新行 字符是数据域的一部分(即文件名),还是作为最后标记的 新行 记号。但使用NUL字节(\0)来作为行终止符就将排除掉这种混淆的情况,使得我们最后可以得到期望的结果:
#我被告知在某些旧版的`tr`程序中需要使用`\000`而不是`\0`来代表NUL字符(假如你需要这种改变请让我知晓!)
sh$ find . -name '*.txt' -printf "%f\0" | cut -z -c 1-5| tr '\0' '\n'
BALAN
EMPTY
BALAN
通过上面最后的例子,我们就达到了本文的最后部分了,所以我将让你自己试试-printf后面那个有趣的"%f\0"参数或者理解为什么我在管道的最后使用了tr命令。
使用cut命令可以实现更多功能
我只是列举了cut命令的最常见且在我眼中最基础的使用方式。你甚至可以将它以更加实用的方式加以运用,这取决于你的逻辑和想象。
以上就是小编今天为大家分享的关于Linux系统中cut命令实用示例的文章,希望本篇文章能够对喜欢Linux系统的你有所帮助,如果你想要了解更多Linux相关信息记得关注达内Linux培训官网哦。
英文:Sylvain Leroux,翻译:Linux中国/Chang Liu
#/article-9895-1.html
SHAPE\* MERGEFORMAT
*声明:内容与图片均来源于网络(部分内容有修改),版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。
填写下面表单即可预约申请免费试听! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!
Copyright © 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有