高级文本命令

高级文本命令

1 sed 命令

synopsis - 基本用法

sed [OPTION]… {script-only-if-no-other-script} [input-file]..

参考链接

博客园 sed 的工作原理

man sed

option
1
2
3
# suppress automatic printing of pattern space
# 关闭 pattern space 的自动打印,如果 command 中已经指定了 p, 可能就需要指定该选项
-n, --quiet, --silent
script command
1
2
3
4
5
6
7
8
9
10
11
12
13
d      # Delete pattern space.  Start next cycle.

# Copy 即覆盖, append 即追加
h H # Copy/append pattern space to hold space.
g G # Copy/append hold space to pattern space.
x # Exchange the contents of the hold and pattern spaces.

n N # Read/append the next line of input into the pattern space.
p # Print the current pattern space.
P # Print up to the first embedded newline of the current pattern space.

w filename # Write the current pattern space to filename.
W filename # Write the first line of the current pattern space to filename. This is a GNU extension.
examples
  • 逆序打印文件行,等同于 tac
1
2
3
4
5
6
7
8
9
# 第一行,只将 pattern space 中的内容 copy 到 hold space,再删除 pattern space,下一轮循环
# 接下来,每一行,将 hold space 的内容 append 到 pattern space,再删除 pattern space,下一轮循环
sed '1{h;d};G;h;$!d' seq.txt


sed '1!G;h;$!d' t.txt
# 1!G —— 只有第一行不执行G命令,将hold space中的内容append回到pattern space
# h —— 第一行都执行h命令,将pattern space中的内容拷贝到hold space中
# $!d —— 除了最后一行不执行d命令,其它行都执行d命令,删除当前行
  • 指定要处理的行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 打印第一行,1 指定第一行
sed -n '1p' seq.txt

# 打印除了第一行的所有行
sed -n '1!p' seq.txt

# 打印奇数行,如第 1 行,第 3 行,第 5 行..., 语义为,从第一行开始,每隔 2 行打印
sed -n '1~2p' seq.txt

# 打印偶数行, 语义为每隔 2 行,打印
sed -n '0~2p' seq.txt

# 打印第一行,以及后续连续的 5 行
sed -n '1,+5p' seq.txt

# 打印第一行,直到第一次遇到行号是 2 的倍数的行,打印
sed -n '1,~2p' seq.txt

# 打印最后一行
sed -n '$p' seq.txt

# 打印
sed -n '/regex/p' seq.txt

# & 代表匹配的内容
echo {a..z} | xargs -n 1 | sed "s/[a-z]/[&]/g"

sed表达式通常用单引号来引用。不过也可以使用双引号。 shell会在调用sed前先扩展双引
号中的内容。如果想在sed表达式中使用变量,双引号就能派上用场了。

2 cut

synopsis - 基本用法

cut OPTION [FILE]

1
2
3
4
5
-d, --delimiter		# 指定列分隔符
-f, --fields # 制定要打印的列(不包含列分隔符的行同样会被打印,除非指定 -s)
--complement # 打印指定之外的列、字符或字节(相当于取反)
-output-delimiter # 指定打印时的列分隔符
-z # 行分隔符为 NUL, cut 是对一行进行切割的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cut -d "," f 2,5 file		# 打印第2列,第5列
cut -d "," f 2-5 file # 打印第2列到第5列
cut -d "," f 2-5 --complement file # 打印除了2-5列的其他列

# 制定打印第几个字节、字符、列(区间)
# N-M 从第N个字节、字符或字段开始到行尾
# N- 从第N个字节、字符或字段开始到第M个(包括第M个在内)字节、字符或字段
# -M 从第1个字节、字符或字段开始到第M个(包括第M个在内)字节、字符或字段

echo {a..z} | cut -d " " -f-4
# result: a b c d


echo -e "4 3\x001 2" | cut -z -d " " --output-delimiter "#" -f1,2

3 awk

awk BEGIN{} PATTERN {} END{}

1
2
3
4
5
# awk 的 pattern
$ awk 'NR < 5' # 行号小于5的行
$ awk 'NR==1,NR==4' # 行号在1到5之间的行
$ awk '/linux/' # 包含模式为linux的行(可以用正则表达式来指定模式)
$ awk '!/linux/' # 不包含模式为linux的行
1
awk -F":" 'BEGIN{OFS="#"} {print $1,$2}'
1
2
3
4
5
6
7
8
# awk可以调用命令并读取输出。把命令放入引号中,然后利用管道将命令输出传入getline:
awk 'BEGIN {FS=":"} { "grep root /etc/passwd" | getline; \
print $1,$6 }'

root /root


awk '/import.+/ { print "echo "$0"|grep -o com"|"bash" }' import

4 tr

1
2
3
4
5
6
7
8
9
10
11
 alnum:字母和数字。
 alpha:字母。
 cntrl:控制(非打印)字符。
 digit:数字。
 graph:图形字符。
 lower:小写字母。
print:可打印字符。
 punct:标点符号。
 space:空白字符。
 upper:大写字母。
 xdigit:十六进制字符。

Vim Editiong Basics

Editing Basics

Buffers

当你编辑一个文件时, Vim 将磁盘上的文件内容读取到计算机的 RAM 中. 这意味这文件的一个备份被存储到了计算机的内存中,你对文件做出的任何修改,是直接映射到内存中的,并且会立即展示出来. 一旦完成了编辑,你可以保存文件,这意味着 Vim 将内存中的内容要写回磁盘. 临时存储文件的内存被称为缓存 (Buffer) . 所以,这就是为什么编辑完后要保存文件的原因.

Swap

当你编辑文件时, 会在相同目录下会创建一个类似于 .hello.txt.swp 的文件. 查看确切的文件名可以使用如下命令:

1
:swapname

Vim 维护了文件缓存区 (buffer) 的一个文件备份, 会定期保存到该文件中, 以避免出现问题时进行恢复 (如 计算机或者 Vim 程序崩溃). 这个文件被称为 swap file, 因为 Vim 会不断交换内存中的内容到磁盘上的文件中.

Save my file

当你修改了文件时, 并且没有保存, 在另一个窗口查看时, 此时是不会看到之前修改的内容的, 这也很好理解, 因为 Vim 只修改了 buffer 中的内容, 还没有保存到磁盘上. 可以通过 :write 命令来完成保存过程, 将内存中的内存写到磁盘中.

为了让生活变得容易一点, 你可以使用快捷键映射, 在 vimrc 文件中:

1
2
3
#To save,ctrl-s.
nmap <c-s> :w<CR>
imap <c-s> <Esc>:w<CR>a

Cut, Copy and paste

desktop vim word operation
cut delete d
copy yank y
paste put p

剪切操作在 Vim 中意味着从 buffer 中删除文本, 并将其存储到寄存器中

复制操作中同样也意味着拉取 (yank) 文本,并将其放到寄存器中

粘贴没有其特殊含义

Why yank not copy ?

Yanking is just a Vim name for copying. The “c” letter was already used for the change operator, and “y” was still available. Calling this operator “yank” made it easier to remember to use the “y” key.

– Vim User Manual

如何在 Vim 中指定要在哪些文本上应用 cut/copy/paste 操作?

你可以:

command operation
dl 删除单个字符
dw 删除一个单词
dd 删除一行
d$ 删除光标位置到行尾的所有字符
dwwP 交换两个单词
…… ……

是不是很棒, 这里最让人激动的事情在于, 你可以结合之前的移动等命令, 更多的可能不是吗? 一般的编辑器, 可能没有办法做到如此简洁, 有力 , 并保持方便记忆 !

还记得清空当前文档的操作吗 ?

words

  • w
  • e

Sentence

  • (
  • )

Paragraph

  • {
  • }

Visual Mode Select

  • aw

    select a word

  • ap

    select a paragraph

  • ab

    select a block (anything with a pair of parentheses)

  • a"

    select a quoted string (like “this is a quoted string”)

Marking your territory

  • create a mark where you can jump here later

    ma (a-zA-Z ) then you create a mark called ‘a’

  • return the cursor to the mark

    'a take you to the exact line and column of the mark

Time machine using undo/redo

  • u undo :leftwards_arrow_with_hook:

  • ctrl-r redo :arrow_right_hook:

  • more…

1
2
3
4
5
6
7
8
9
10
11
# take you back by 4 minutes
:earlier 4m

# take you later by 45 seconds
:later 45s

# go back 5 changes
undo 5

# view the undo tree
undolist

Powerful search engine

  • basic search*
1
2
3
4
5
6
7
8
# search
/the<cr> # /the followed by the enter key

# next occurrence
press `n`

# previous occurrence
press `N`

还记得我们第一次介绍 Vim 的骚操作吗 * 搜索当前光标所在的单词

  • start searching as and when you type the search phrase
1
set incsearch
  • ignore the case of the text that you are searching for
1
set ignorecase
  • smart case
1
set smartcase
  • searching for /step, then it will search for any combination of upper and lower case text. So eventually, “Step”, “Stephen”,”stepbrother”
  • searching for /Step, only “Step”, “Stephen”, but not “stepbrother”
  • basic patterns
1
2
3
4
5
6
7
# exactly search the word 'step' and not 'stepfather'
/\<step\>

# search numbers
/\d\+

# see :help pattern for more details

永远不要去死记硬背这些操作, 试着理解, 并练习, 最终成为肌肉记忆.

vim doc

Vim Powerful Commands

` Vim Versatile commands

  • .

    • 通过 Normal Mode (命令行模式)下的 . 命令来重复上一次的修改操作. 上一次的修改意味着很多操作:Normal Mode 下删除一个或多个字符,单词,行, 如 x, dw, d$, dd 等;比如改变缩进的命令:>>, << 等.

    • 命令行模式下大多数命令对文本做出的修改,都可以使用 . 来重复;Insert Mode (编辑模式) 下做出的修改的重复取决于进入该模式的命令,加入,你通过 A 进入 Insert Mode, 在末尾添加了单词 end,按下 Esc, . 命令会在末尾重复添加单词 end.

1
2
3
4
5
6
7
8
9
10
11
12
#1. You are now in Normal Mode, you have the text like those below:

This is

# 2. Press A to enter Insert Mode and write text `end`
This is end

# 3. Press Esc to enter Normal Mode and press .(period), things will happen like this:
This is end end

# 4. mode dot, mode ` end`
This is end end end
  • 5. 表示重复执行多少次
  • Motions (光标的移动) 并不会改变任何事情,所以 . 并不会进行重复

Redis Cluster

consistent hashing 一致性哈希

hash slot 哈希槽

Redis 集群的数据分片

Redis 集群没有使用一致性哈希,而是使用了一种不同的数据分片形式: 每个键在概念上属于所谓的 哈希槽 (hash slot) 的一部分。Redis 集群一共有 16384 个哈希槽,计算一个给定键的哈希槽,采取的方式是:键的 CRC16 对 16384 取模

Redis 集群中的每一个节点都负责所有哈希槽的一个子集. 假如一个集群有三个节点,那么哈希槽的分布可能是这样:

  • A 节点的哈希槽范围 0 - 5500
  • B 节点的哈希槽范围 5501 - 11000
  • C 节点的哈希槽范围 11001 - 16383

这使得很容易在集群中增加和移除节点。例如,如果想增加一个新节点 D, 需要将 A,B,C 中的某些哈希槽移动到 D. 类似的,如果想要移除节点 A, 可以将 A 负责的哈希槽移动到 B 和 C。

由于从一个节点向另一个节点移动哈希槽并不需要停止操作,增加和移除节点或者改变节点所负责的哈希槽的百分比,不需要任何的宕机操作。

Redis 集群支持多个键的操作(多个键的同时操作),只要单个命令的执行(Redis事务,Lua脚本执行)所涉及的这些键,都属于同一个哈希槽。用户可以通过使用一个叫做 哈希标签 (hash tags) 的概念,强制多个键属于不同的哈希槽.

Redis 集群规范中记录了哈希标签,但重点是,如果键中的 {} 括号之间有一个子字符串,则仅对字符串内的内容进行哈希处理,例如 this{foo}keyanother{foo} key 保证在同一个哈希槽中,并且可以在一个命令中一起使用多个键作为参数。

Redis 集群的主复制模式

Redis Cluster master-replica model

为了保证可以用性, 当主节点中的某些节点出现故障或者不能与集群中的大多数节点进行通信时, Redis 集群使用了一种主节点复制模式, 即每个哈希槽都有一个到 N 个副本 (N-1 个额外的副本节点).

在上述 A, B, C 三个节点的集群中, 如果节点 B 故障, 集群不能与之通信, 那么就不再能够提供哈希槽 0-5500 范围的服务. 然而, 如果在创建集群的时候, 为每一个主节点增加一个副本节点, 那么最终集群包含了 A, B, C 以及对应的副本节点 A1, B1, C1. 这样, 系统就能够在 B 节点故障时继续提供服务.

B1 节点复制节点 B, 如果 B 节点故障, 集群将使得 B1 成为新的主节点继续正确地提供服务. 但是如果 B1 和 B 同一时间都故障了, 那么集群就不能继续服务.

Redis 集群地一致性保障

Redis Cluster consistency guarantees

Redis Cluster 无法保证强一致性。实际上,这意味着在某些条件下,Redis Cluster 可能会丢失系统已向客户端确认的写入。

Redis Cluster 会丢失写入的第一个原因是因为它使用异步复制。这意味着在写入期间会发生以下情况:

客户端写入主 B。
主 B 向客户端回复 OK。
主 B 将写入传播到其副本 B1、B2 和 B3。

B 在回复客户端之前不会等待来自 B1、B2、B3 的确认,因为这对 Redis 来说是一个令人望而却步的延迟惩罚,因此如果您的客户端写入某些内容,B 会确认写入,但会在此之前崩溃能够将写入发送到其副本,其中一个副本(未收到写入)可以提升为主,永远失去写入。

这与大多数配置为每秒将数据刷新到磁盘的数据库发生的情况非常相似,因此您已经能够推理出这种情况,因为过去使用不涉及分布式系统的传统数据库系统的经验。同样,您可以通过强制数据库在回复客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致性能低得令人望而却步。在 Redis Cluster 的情况下,这相当于同步复制。

基本上,需要在性能和一致性之间进行权衡。

Redis 集群在绝对需要时支持同步写入,通过 WAIT 命令实现。这使得丢失写入的可能性大大降低。但是请注意,即使使用同步复制,Redis Cluster 也没有实现强一致性:在更复杂的故障场景下,始终有可能将无法接收写入的副本选为 master。

还有一个值得注意的场景是 Redis 集群将丢失写入,这发生在网络分区期间,客户端与少数实例(至少包括一个主实例)隔离。

以我们的 6 个节点集群为例,它由 A、B、C、A1、B1、C1 组成,有 3 个主节点和 3 个副本节点。还有一个客户端,我们将其称为 Z1。

分区发生后,可能在分区的一侧有 A、C、A1、B1、C1,而在另一侧有 B 和 Z1。

Z1 仍然能够写入 B,B 将接受其写入。如果分区在很短的时间内愈合,集群将继续正常运行。但是,如果分区持续足够的时间让 B1 在分区的多数侧提升为 master,则 Z1 同时发送给 B 的写入将丢失。

请注意,Z1 能够发送到 B 的写入量有一个最大窗口:如果分区的多数方已经有足够的时间来选举一个副本作为主节点,那么少数方的每个主节点都将停止接受写入。

这个时间量是Redis Cluster一个非常重要的配置指令,称为节点超时。

节点超时后,主节点被视为出现故障,并且可以由其副本之一替换。类似地,在节点超时过后,主节点无法感知大多数其他主节点,它会进入错误状态并停止接受写入。

Redis 的持久化

Redis 数据持久化

Redis 中,有两种方式将数据持久化到磁盘。一种称之为快照 (Spapshotting), 将某一时刻存在数据写道磁盘。另外一种称之为AOF 仅追加文件 (append-only file), 它是将到来的所有写命令写到磁盘上。这两种方式可以一起使用,也可以单独使用,或者不适用持久化。

选择配置数据持久话的原因还是为了在 Redis,Redis 所在的主机硬件等发生故障时进行恢复,可以将持久化的文件在远程主机进行备份,故障时进行恢复。而且,如果 Redis 中的数据时大数据集上的聚合分析结果,没有备份,重新计算的代价可能时无法接受的。

通过快照将数据持久化到磁盘

在 Redis 中,可以通过创建一个快照,创建一个内部中数据的某个时刻的副本,这些副本可以被备份,复制到其它服务器,创建一个 Redis 服务器的副本,或者留着待以后重启使用。

基本的配置选项

1
2
3
4
5
6
save 60 1000 # 创建快照的频率。如果自上一次创建快照后的 60s 内有 1000 此写,就开启新的快照
stop-writes-on-bgsave-error no # 当创建快照时,发生异常是否暂停所有的写命令
rdbcompression yes # 是否进行压缩
dbfilename dump.rdb # 快照文件名

dir ./ # 快照文件保存的目录

自上一次快照完成后,直到下一次快照开始执行,这个期间,如果 Redis 崩溃,系统或硬件故障,期间的写数据就会丢失。

五种初始化快照的方法

  1. 任何的 Redis 客户端都可以通过调用 BGSAVE 命令来初始化快照(创建快照)。在所有支持 BGSAVE 的平台上,Redis 会进行进程的 fork, 子进程会将快照写到磁盘,而父进程仍然可以用于响应来自客户端的命令。

    当一个进程 fork 时,底层的操作系统会创建该进程的一个副本。在 Unix 和类 Unix 的系统上,复制进程会被优化:最开始,子进程和父进程时共享所有内存的。当父进程或子进程开始写内存,内存将不再共享。

  2. Redis 的客户端可以通过调用 SAVE 命令来初始化快照,这将导致 Redis 停止对任何命令的响应,直到快照完成。不经常使用,但是如果对于该这种等待 ok 或者没有足够的内存执行 BGSAVE 操作,可以使用。

  3. 如果 Redis 配置了 save 选项,如 save 60 10000, 如果自上一次快照成功后的 60s 内,已经有 10000 个写操作,那么 Redis 会自动触发一个 BGSAVE 操作。可以配置多个 save 行,任意一条规则满足,就会触发 BGSAVE 操作。

  4. 当 Redis 接受到 SHUTDOWN 终止命令时,或者接受到标准的 TERM 信号,Redis 会执行 SAVE 操作,阻塞任何客户端执行任何命令,然后再终止。

  5. 当一个 Redis 服务器连接到另一个 Reids 服务器,并且发起了 SYNC 同步命令开始复制(replication), Master Redis 服务器将会开始一个 BGSAVE 操作,如果当前没有一个正在进行中的 BGSAVE 操作或者最近完成了。

只追加写文件持久化 (Append-only file persistence)

只追加写文件通过将修改写到文件的末尾,保存了数据发生修改时的记录。这样,任何人都有可以通过从头到尾,重放 (replay) 只追加写日志文件就可以恢复整个数据集。可以通过设置配置项 appendonly yes 开启。

文件同步

当写文件到磁盘上时,至少发生了三件事:首先是写缓存,当调用 file.write() 或其它语言中等价的命令时会执行该操作。当数据存在缓冲区中时,操作系统可以将这些数据再未来的某个时刻写到磁盘。可以通过调用 file.flush(), 要求操作系统将数据写到磁盘上,但这只是发给操作系统的一个请求,并不会立即执行。由于数据实际时不在磁盘上的直到操作系统将它写到磁盘上,我们可以告诉操作系统同步文件到磁盘上,这将导致阻塞 (block),直到同步完成。当同步完成时,我们可以确定数据此时是在磁盘上的,并且如果系统故障,可以稍后读取进行恢复。

appendfsync always

如果设置了 appendfsync always, Redis 的每次写将会导致一个磁盘的写过程,如果 Reid 崩溃了,这可以极大地减少数据丢失。然而,因为每次都有写磁盘这个过程,整体的性能会受限于此磁盘的性能。

appendfsync eversec

作为保持数据安全和保持高写入性能之间的合理折衷,我们还可以设置 appendfsync everysec。 此配置将每秒同步一次仅追加日志文件。 对于大多数常见用途,与不使用任何类型的持久性相比,我们可能不会发现每秒同步到磁盘的显着性能损失。 通过每秒同步到磁盘,如果系统崩溃,我们最多可能会丢失一秒钟已在 Redis 中写入或更新的数据。 此外,在磁盘无法跟上正在发生的写入量的情况下,Redis 会优雅地减速以适应驱动器的最大写入速率。

appendfsync no

Redis 并不会显示地执行任何文件同步,而是将这一切交给操作系统。这种情况下应该没有性能损失,但如果系统以某种方式崩溃,我们将丢失未知和不可预测的数据量。 如果我们使用的硬盘驱动器对于我们的写入负载来说不够快,Redis 会运行良好,直到将数据写入磁盘的缓冲区被填满,此时 Redis 会因为被阻止写入而变得非常慢。 通常不鼓励使用此配置选项。

重写/压缩 AOF

AOF 看起来比较完美,即能将数据损失降低到 1s,又可以最小化数据持久化到磁盘上地时间。但是问题是,Redis 的每次写命令,都会生成一条日志记录,随着时间的推移,AOF 日志文件的大小会不断增长,可能会导致磁盘空间耗尽。但更常见的问题是,在 Redis 重启时,由于需要按顺序执行 AOF 中的每条命令,处理较大的文件,需要花费更长的时间

此时可以使用 BGREWRITEAOF, 它会通过移除冗余的命令,来尽可能地使得 AOF 变得更小一点。和 BGSAVE 命令一样,这个命令也会执行一次 fork 过程,由子进程完成 AOF 的重写,所以关于 fork 的时间,内存使用的问题,同样也适用于 BGREWRITEAOF。而且更糟的时,当 AOF 被重写,操作系统需要删除几十 G 的 AOF 文件时,会使的操作系统中断几秒。

当启用 AOF , 并且满足以下两个配置项时,Redis 会启动一次 BGREWRITEAOF, 如果 AOF 重写的频率过高,可以适当增加 auto-aof-rewritepercentage, 但是可能会导致 Redis 启动需要花费更多的时间

1
2
3
4
5
# 当 AOF 至少比 Redis 上次完成重写时的 AOF 大 100% 时
auto-aof-rewritepercentage 100

# 当 AOF 的大小至少为 64m 字节时
auto-aof-rewrite-min-size 64mb

复制 Replication