Allen's Notes

Quik notes


  • 首页

  • 归档

javascript类型转换

发表于 2019-02-28 | Edited on 2025-04-24

以下内容是读《YOU DON’T KNOW JS》系列的《types & grammar》第 4 章节的笔记。

抽象值运算(Abstract Value Operations)

“abstract operations” 通俗的理解就是“仅内部使用的操作”。在 ES5 specification 内定义了 9 个 abstract value operations ,其中作者重点介绍了 ToString ToNumber 和 ToBoolean。

ToString

任何非字符串类型的值被转为 string 时,都会经过 ToString 的处理。

基本类型 ToString

基本类型ToString运算的规则很简单,基本上原来什么样,转换后就是什么样的。但是需要注意特大或特小的数字转成字符串时会变成指数的形式。

1
2
3
4
5
6
7
8
String(false); // "false"
String(undefined); // "undefined"
String(null); // "null"
String(10); // "10"
String(Symbol("hello")); // "Symbol(hello)"

var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
String(a); // "1.07e21"

对象 ToString

一般对象的ToString运算会调用默认的toString方法(因为大部分对象原型链顶部是Object.prototype,它内部有toString方法;它会返回内部的[[Class]],例如 "[object Object]")。

如果重写了对象的toString方法,那么就调用重写后的toString。注意重写的toString方法只能返回基本数据类型,返回对象会抛出异常。

还有一种特殊情况就是用Object.create(null)创建以null为原型的对象。它的原型链上没有toString,执行ToString运算时会抛出异常。

ToNumber

任何非 number 类型的值被转为 number 时,都会经过 ToNumber 的处理。

基本类型 ToNumber

基本数据类型转 number 的规则如下。注意null 转成数字后是 0 而不NaN,空字符串转成数字后是 0,symbol 不能转数字,否则报错。

1
2
3
4
5
6
7
8
+null; // 0
+undefined; // NaN
+true; // 1
+false; // 0
+"5"; // 5
+"5allen"; // NaN
+""; // 0
+Symbol(10); // 抛出异常

对象 ToNumber

对象转 number 时,会先执行一个ToPrimitive的抽象值运算。执行ToPrimitive后返回的基本数据类型再按前面提到的基本数据类型转 number 的规则转换。

ToPrimitive运算会先尝试调用valueOf方法,如果返回的结果是基本数据类型就返回运算结果;如果对象上没有valueOf方法或valueOf返回的结果不是基本数据类型就尝试调用toString方法;如果toString方法不存在或toString方法返回对象,会抛出异常。

ToPrimitive抽象值运算的流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
graph TD
A(start) -->B{valueOf存在吗}
B -->|存在| C[执行valueOf]
B -->|不存在| F[执行valueOf]
C -->D{返回结果是基本类型吗}
D -->|是| E(返回运算结果)
D -->|否| F{toString存在吗}
F -->|存在| H[执行toString]
F -->|不存在| G(抛出异常)
H -->I{返回结果是基本类型吗}
I -->|是| J(返回运算结果)
I -->|否| K(抛出异常)

ToBoolean

强转为boolean后的结果为false的值,称为falsy value。以下是所有的falsy value。

  • undefined
  • null
  • false
  • +0 -0 NaN
  • ""

不在以上列表中的所以其他值都是truthy value,也就是说其他值在强转为boolean后的结果都是true。

显式转换(Explicit Coercion)

我们知道很多操作会在内部对值进行转换,但是为了让自己和他人更直观地阅读代码,我们最好用显式的方式来对值进行转换。

显式:Strings <--> Numbers

1
2
3
4
5
6
7
8
var a = 42;
var b = String(a);

var c = "3.14";
var d = Number(c);

b; // "42"
d; // 3.14
1
2
3
4
5
6
7
8
var a = 42;
var b = a.toString();

var c = "3.14";
var d = +c;

b; // "42"
d; // 3.14

string 和 number 间的转换,作者认为String(..) Number(..) toString()这几个方法是显式的转换。+ "3.14"这种形式的转换在 js 社区内普遍也被认为是一种显式的转换,但是该方法在某些场合会让代码变得比较难以理解,例子如下:

1
2
3
4
5
6
var c = "3.14";
var d = 5 + +c;

d; // 8.14

1 + -+(+(+-+1)); // 2

总结:作者推荐使用Sting(..) Number(..) toString()和一元操作符+来显示地将其他值转为 number 和 string。避免在某些会引起误解的地方使用一元操作符+。

显式:Parsing Numeric Strings

1
2
3
4
5
6
7
8
var a = "42";
var b = "42px";

Number(a); // 42
parseInt(a); // 42

Number(b); // NaN
parseInt(b); // 42

除了用Number(..)方法进行转换,我们还可以用parseInt(..)(还有它的双胞胎parseFloat(..))来将其他值解析为 number。parseInt(..)会从左到又解析,直到碰到非数字字符;而Number(..)方法(或其他转换方法)转换的字符串中含非数字字符就会返回NaN。

在使用parseInt(..)时,作者推荐大家仅对 string 进行解析。不然parseInt(..)会将值先进行ToString操作(前面介绍过的抽象值操作),然后再进行解析;如果这样,这里面就带有隐式的转换,可能会带来意想不到的结果,例子如下。

1
2
3
4
5
6
7
parseInt(0.000008); // 0   ("0" from "0.000008")
parseInt(0.0000008); // 8 ("8" from "8e-7")
parseInt(false, 16); // 250 ("fa" from "false")
parseInt(parseInt, 16); // 15 ("f" from "function..")

parseInt("0x10"); // 16
parseInt("103", 2); // 2

同时作者还提示大家,当需要解析的字符串中以0x开头,那么parseInt(..)会以十六进制的方式来解析(0o 0b同理)。如果明确要以 10 进制来解析,推荐大家在parseInt(..)的第二个参数内传入10,来明确告知以 10 进制的方式解析。

总结:作者推荐大家仅对 string 使用parseInt(..),在必要的情况下可以在第二个参数传10,来确保它以十进制的方式解析。

显式:* –> Boolean

和String(..) Number(..) 一样,Boolean(..)是一个显式的转换方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g = undefined;
var h = NaN;

Boolean(a); // true
Boolean(b); // true
Boolean(c); // true

Boolean(d); // false
Boolean(e); // false
Boolean(f); // false
Boolean(g); // false
Boolean(h); // false

虽然Boolean(..)是显式的方式,但在平时的开发中并不常用。就像前面提到的用一元操作符+将值转为 number,我们会用!来将值转为 boolean。!会将值转为 boolean,同时也反转了 boolean 的值,所以更通用的方式是!!。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g = undefined;
var h = NaN;

!!a; // true
!!b; // true
!!c; // true

!!d; // false
!!e; // false
!!f; // false
!!g; // false
!!h; // false

总结:作者推荐使用Boolean(..) !!来显示转化为 boolean。!!会在开发中比较常用。

隐式转换(Implicit Coercion)

所有对 JavaScript 类型转换的抱怨主要来自它的隐式转换。

《JavaScript 语言精粹》的作者,Douglas Crockford 在很多会议和文字上表示应该避免使用 JavaScript 类型转换。但本书的作者认为 Douglas Crockford 想表达的确切意思应该是避免使用 JavaScript 的隐式转换。

然后作者提出以下几个问题:

So, is implicit coercion evil? Is it dangerous? Is it a flaw in JavaScript’s design? Should we avoid it at all costs?

大意如下:

所以,隐式转换是魔鬼吗?它很危险吗?它是不是 JavaScript 设计上的瑕疵?我们是否要不惜一切代价避免使用它呢?

大多数的读者会一边倒地回答”Yes!”。

作者认为我们需要从不同的角度去看隐式转换,否则就太狭隘了,这样会失去很多重要的细节。

他明确了使用隐式转换的目的是为了减少冗余的、模板化的代码,避免不必要的实现细节。

可以肯定的是,隐式转换内含有很多潜在的危险,我们需要学会避开那些会危害代码健壮的坑,而不是全盘抛弃隐式转换。

以下是隐式转换中比较好的实践。

隐式:Strings <--> Numbers

+ 操作符即可作为算数运算中的加号,也可以作为字符串的拼接符号。

1
2
3
4
5
6
7
8
var a = "42";
var b = "0";

var c = 42;
var d = 0;

a + b; // "420"
c + d; // 42

基本类型中做+操作:+两边其中一方是 string,那么执行的是字符串拼接,否则都是做算法运算。

含对象的+操作:对象都会先经过ToPrimitive抽象值运算得到基本类型,然后再按基本类型的规则做+运算。

a + ""是我们常用的转 string 方法,但值得注意的是它和我们前面提到的显式转 string 的方法String(..) toString并不等同,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
var a = {
valueOf: function() {
return 42;
},
toString: function() {
return 4;
}
};

a + ""; // "42"

String(a); // "4"

上面的例子中,a是一个对象,并且重写了valueOf toString方法。a + ""时,a要运行ToPrimitive运算,返回结果 42,然后再与空字符串拼接。而String( a )内部运行ToString运算,所以直接调用a.toString()。

一般情况,我们不会碰到这种陷进,除非我们在对象上重写了valueOf和toString方法。

- * / 操作只可作为算数运算。所以可以用来将字符串转数字,例 a - 0 a * 1 a / 1。

总结:相比于 String(a) a + ""在开发中会更常用。

隐式:* –> Boolean

以下表达式操作会隐式地将值转为boolean。

  • if ( .. )
  • for ( .. ; .. ; .. )
  • while ( .. ) do..while( .. )
  • 三元运算符 ? :
  • 逻辑或 || 逻辑与 &&

|| 和 &&

这两个运算符的结果未必一定是boolean,它会返回运算符两边中的一个值。

1
2
3
4
5
6
7
8
9
var a = 42;
var b = "abc";
var c = null;

a || b; // 42
a && b; // "abc"

c || b; // "abc"
c && b; // null

宽松相等== 与 严格相等===

严格相等:等号两边的值和类型都会进行比较;如果等号两边是对象,比较对象的引用地址。

宽松相等:只比较等号两边的值,如果类型不同会先转化再比较;如果等号两边是对象,比较对象的引用地址。

宽松相等==

严格相等没什么好说的,最容易让人迷惑的是宽松相等==。大多情况下,它是瑕疵,它让代码容易出错。

宽松等于两边值类型不同时,其中的一边或两边会进行转换然后再进行比较。转换的规则会有点复杂,而且甚至有点迷惑性。

其中要比较注意的是 falsy values 间的比较。

  • undefine null两个互相宽松相等,但却不与其它 falsy values 宽松相等。
  • NaN与所有其它值都不相等。

宽松相等的规则总结:

  • 如果其中一边是对象,那么先执行ToPrimitive转为基本类型
  • boolean执行ToNumber
  • string 和 number 比较,string 要ToNumber
  • null undefined两宽松相等,和其它不宽松相等

event loop in javascript

发表于 2019-02-28 | Edited on 2025-04-24

浏览器中的 event loop 和 Node.js 中的不一样,分开讨论。

浏览器中的 event loop

一张图看懂浏览器中的 event loop:

浏览器中的event loop

这里有一个调用栈(Call Stack)、一个任务队列(Task Queue)和一个 微任务队列(Microtask Queue)。

异步接口根据类型可以分为宏任务(macrotask 也称 task)和微任务(microtask)。

  • 微任务:Promise mutation observer
  • 宏任务:script setTimeout setInterval xhr UI事件

执行流程

JavaScript 是单线程的,遇到异步接口时它并不会被阻塞,而是将异步接口的回调函数挂起,然后继续执行同步代码。

异步接口会在其它的线程上运行,当异步接口处理完毕,被挂起的回调函数才会被添加到对应的队列中。微任务会被添加到Microtask Queue,宏任务会被添加到Task Queue。

当 JavaScript 脚本(JavaScript 脚本是宏任务)执行完毕,并且调用栈为空时,就会先去取Microtask Queue内的回调函数放到调用栈内执行,执行完后再去取,直到清空Microtask Queue为止。

清空Microtask Queue后,会按情况更新 UI 界面,然后从Task Queue内取出一个任务放到调用栈内执行,执行完毕后按刚才的步骤去清空Microtask Queue,之后就是按前面的规则循环。

总结:浏览器会依次执行每个宏任务,每个宏任务之后会按序清空Microtask Queue并根据需要更新 UI。

练习

以下练习来自知乎内的一篇文章,参考链接在底部。

练习一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1);

setTimeout(() => {
console.log(2);
}, 0);

Promise.resolve()
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});

console.log(5);
// Google Chrome v72 内运行结果
// 1 5 3 4 2
练习二
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
27
28
29
30
console.log(1);

setTimeout(() => {
console.log(2);
new Promise(resolve => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
});
});

new Promise(resolve => {
console.log(7);
resolve();
}).then(() => {
console.log(8);
});

setTimeout(() => {
console.log(9);
new Promise(resolve => {
console.log(11);
resolve();
}).then(() => {
console.log(12);
});
});
// Google Chrome v72 内运行结果
// 1 7 8 2 4 5 9 11 12

Node 中的 event loop

Node.js 中的 event loop 实现和浏览器中的不同。下图是 Node 中 event loop 的实现:

eventloop_in_node.jpg

Node 中的 event loop 有多个阶段,每个阶段都有对应的任务队列(Task Queue)。如图所示有:

  • expired timers/intervals queue:即到期的 setTimeout和setInterval
  • IO events queue:包括文件读取、网络等 IO 操作
  • immediates queue:通过setImmediate注册的函数
  • close handlers queue: 各种资源关闭的回调函数,比如 TCP 连接断开

Node 中的微任务队列(Microtask Queue)也不只一个,每个 event loop 阶段之后会清空所有的微任务队列,如图所示有:

  • next tick queue: 通过process.nextTick注册的函数,优先被清空
  • other micro tasks queue: 其它微任务,常见的如Promise

Node 中异步接口

  • 微任务:Promise process.nextTick
  • 宏任务:script setTimeout setInterval setImmediate 各类 IO 相关的异步接口 和 各类资源关闭的异步接口

流程

按顺序执行每个阶段,每个阶段会清空对应的任务队列(Task Queue),每个阶段之后会清空微任务队列(先next tick queue再other micro tasks queue)

与浏览器的 event loop 不同的地方:

  • 清空每个阶段对应的任务队列后再清空微任务队列
  • 有多个任务队列和微任务队列

练习

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
console.log(1);

setTimeout(() => {
console.log(2);
new Promise(resolve => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
});
process.nextTick(() => {
console.log(3);
});
});

new Promise(resolve => {
console.log(7);
resolve();
}).then(() => {
console.log(8);
});

process.nextTick(() => {
console.log(6);
});

setTimeout(() => {
console.log(9);
process.nextTick(() => {
console.log(10);
});
new Promise(resolve => {
console.log(11);
resolve();
}).then(() => {
console.log(12);
});
});

// node v10.15.1
// 1 7 6 8 2 4 9 11 3 10 5 12

参考

  • 知乎文章《Event Loop 的规范和实现》,大量参考了这篇文章。
  • 掘金小册《前端面试之道》的第七章节《Event Loop》
  • Tasks, microtasks, queues and schedules

Linux中格式化输出

发表于 2019-02-28 | Edited on 2025-04-24

目录

  • 格式化打印:printf
  • 数据处理工具:awk
  • 文件对比工具:diff cmp patch
  • 文件打印:pr

格式化打印:printf

我们在 bash 中想输出一个表格,但由于每个字段的长度不同,会导致输出的样子像下面这样,很乱,不方便看。

1
2
3
4
5
# printf.txt
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33

我们想让表格格式化成下面那样,我们就可以用 printf 命令了。

1
2
3
4
Name   Chinese  English  Math  Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
printf '打印格式' 实际内容
#选项与参数:
#关于格式方面的几个特殊样式:
# \a 警告声音输出
# \b 倒退键(backspace)
# \f 清除屏幕 (form feed)
# \n 输出新的一行
# \r 亦即 Enter 按键
# \t 水平的 [tab] 按键
# \v 垂直的 [tab] 按键
# \xNN NN 为两位数的数字,可以转换数字成为字符。
#关于 C 程序语言内,常见的变量格式
# %ns 那个 n 是数字, s 代表 string ,亦即多少个字符;
# %ni 那个 n 是数字, i 代表 integer ,亦即多少整数码数;
# %N.nf 那个 n 与 N 都是数字, f 代表 floating (浮点),如果有小数码数,假设我共要十个位数,但小数点有两位,即为 %10.2f 啰!
1
2
3
4
5
#例 将刚才的print.txt格式换成好看的表格
printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)

#例 将刚才的print.txt的标题去掉,并将每列固定长度
printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)

printf 另一个用处是将 16 进制显示为 ASCII

1
2
#例
printf '\x45\n'

数据处理工具:awk

1
awk '条件类型1{动作1} 条件类型2{动作2} ...' filename

内置变量

变量名 意义
NF 每一行 (\$0) 拥有的字段总数
NR 目前 awk 所处理的是“第几行”数据
FS 目前的分隔字符,默认是空白键
\$N 第几个字段
1
2
#例
last -n | awk '{print $1 "\t lines:" NR "\t columns:" NF}'

BEGIN 和 END

1
2
3
4
#例 awk默认分隔符是空格,但现在我们想改成‘:’。我们可以设置FS为‘:’。但下面这样写只有第二行开始生效。
cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t " $3}
#例 为了让它从第一行就开始生效。可以用BEGIN关键字
cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'

awk 还可以对表内的数据做处理。

1
2
3
4
5
# pay.txt
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000

假设我们有上面那个表(pay.txt),想添加一列 Total 做求和。我们可以这样写

1
2
3
4
5
# 下面的‘>’表示换行。在一个动作内写多个命令可以用回车或‘;’隔开。而且这里定义的total变量可以直接使用,不需要要‘$’
cat pay.txt | \
>awk 'NR==1{printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total" }
> NR>=2{total = $2 + $3 + $4
> printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'

awk 动作内{}是支持 if 的,上面的命令也可以写成下面那样

1
2
3
4
cat pay.txt &#124; \
> awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}
> NR&gt;=2{total = $2 + $3 + $4
> printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'

文件对比工具:diff cmp patch

diff

diff 是比较两个文件间的差异,并以行为单位进行比较。

1
2
3
4
5
6
7
8
9
# 用法
diff [-bBi] from-file to-file
#选项与参数:
#from-file :一个文件名,作为原始比对文件的文件名;
#to-file :一个文件名,作为目的比对文件的文件名;
#注意,from-file 或 to-file 可以 - 取代,那个 - 代表“Standard input”之意。
#-b :忽略一行当中,仅有多个空白的差异(例如 "about me" 与 "about me" 视为相同
#-B :忽略空白行的差异。
#-i :忽略大小写的不同。

现在有 passwd.old passwd.new 文件。passwd.old 拷贝自/etc/passwd;passwd.new 是在/etc/passwd 的基础上做了修。删除了第四行,并将第六行替换为‘no six line’。

1
2
3
4
mkdir -p /tmp/testpw
cd /tmp/testpw
cp /etc/passwd passwd.old
cat /etc/passwd | sed -e '4d' -e '6c no six line' > passwd.new

现在我们可以查看新旧的 passwd 的差别了

1
diff passwd.old passwd.new

diff 还以可以比较两目录下的文件名

1
diff /etc/rc0.d/ /etc/rc5.d/
cmp

对比两文件,以字节的地位对比,默认仅会输出第一个发现的不同点。

1
2
3
4
# 用法
cmp [-l] file1 file2
#选项与参数:
#-l :将所有的不同点的字节处都列出来。因为 cmp 默认仅会输出第一个发现的不同点。
patch

该命令可以用来做打补丁的用途,也可以将文件回退到补丁前的状态。
centOS7 默认没有安装 patch 文件,我们需要自己安装。

1
2
3
4
5
su - #切换到root账户
mount /dev/sr0 /mnt #将安装光盘挂载到/mnt
rpm -ivh /mnt/Packages/patch-2.*
umount /mnt
exit

用法

1
2
3
4
#更新
patch -pN < patch_file
#还原
patch -R -pN < patch_file

制作补丁文件。以上面的 passwd.old 和 passwd.new 为例。

1
2
3
4
5
6
#制作补丁文件
diff -Naur passwd.old passwd.new > passwd.patch
#将刚刚制作出来的 patch file 用来更新旧版数据
patch -p0 < passwd.patch
#恢复旧文件的内容
patch -R -p0 < passwd.patch

文件打印:pr

1
2
#打印/etc/man_db.conf
pr /etc/man_db.conf

Linux中的正则

发表于 2019-02-28 | Edited on 2025-04-24

linux 中的正则有特殊符号,其他的内容和 javascript 中都相同。注意语系会对正则产生影响,一般使用与 POSIX 相容的语系。
特殊符号 | 意义
—|—
[:alnum:] | 代表英文大小写字符及数字,亦即 0-9, A-Z, a-z
[:alpha:] | 代表任何英文大小写字符,亦即 A-Z, a-z
[:blank:] | 代表空白键与 [Tab] 按键两者
[:cntrl:] | 代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del.. 等等
[:digit:] | 代表数字而已,亦即 0-9
[:graph:] | 除了空白字符 (空白键与 [Tab] 按键) 外的其他所有按键
[:lower:] | 代表小写字符,亦即 a-z
[:print:] | 代表任何可以被打印出来的字符
[:punct:] | 代表标点符号 (punctuation symbol),亦即:” ‘ ? ! ; : # \$…
[:upper:] | 代表大写字符,亦即 A-Z
[:space:] | 任何会产生空白的字符,包括空白键, [Tab], CR 等等
[:xdigit:] | 代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字符

带正则功能的命令

并不是所有命令都能使用正则,只有支持正则的命令和程序才可以使用。

1.grep 的一些进阶选项
  • 用法
1
2
3
4
5
6
7
8
9
10
11
12
grep [-A] [-B] [--color=auto] '搜寻字串' filename
#选项与参数:
#-A :后面可加数字,为 after 的意思,除了列出该行外,后续的 n 行也列出来;
#-B :后面可加数字,为 befer 的意思,除了列出该行外,前面的 n 行也列出来;
#--color=auto 可将正确的那个撷取数据列出颜色

#用 dmesg 列出核心讯息,再以 grep 找出内含 qxl 那行(鸟哥用的是QXL虚拟显卡,大家可能会搜不到)
dmesg | grep 'qxl'
#承上题,要将捉到的关键字显色,且加上行号来表示
dmsg | grep -n --color=auto 'qxl'
#承上题,在关键字所在行的前两行与后三行也一起捉出来显示
dmesg | grep -n -A3 -B2 --color=auto 'qxl'
  • 例子

鸟哥提供了一个练习用的文件:regular_express.txt

内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.^M
GNU is free air not free beer.^M
Her hair is very beauty.^M
I can't finish the test.^M
Oh! The soup taste good.^M
motorcycle is cheap than car.
This window is clear.
the symbol '*' is represented as start.
Oh! My god!
The gd software is a library for drafting programs.^M
You are the best is mean you are the no. 1.
The world &lt;Happy&gt; is the same with "glad".
I like dog.
google is the best tools for search keyword.
goooooogle yes!
go! go! Let's go.
# I am VBird
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#查找‘the’并带上行号
grep -n 'the' regular_express.txt
#查找没有‘the’的行,并带上行号,这里的v相当于相反
grep -vn 'the' regular_express.txt
#忽略大小写
grep -in 'the' regular_express.txt
#搜索含tast或test的行
grep -n 't[ae]st' regular_express.txt
#搜索带有‘oo’并且前一个字符不为小写字母
grep -n '[^[:lower:]]oo' regular_express.txt
#搜索啥都没有的一行
grep -n '^$' regular_express.txt
#搜索非空行和开头不为‘#’的行
grep -v '^$' /etc/rsyslog.conf &#124; grep -v '^#'
2.sed 工具

sed 是一个管线命令,可以将数据进行取代、删除、新增、攫取。

  • 用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sed [-nefr] [动作]
#选项与参数:
#-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到屏幕上。但如果加上 -n 参数后,则只有经过 sed 特殊处理的那一行(或者动作)才会被列出来。
#-e :直接在命令行界面上进行 sed 的动作编辑;
#-f :直接将 sed 的动作写在一个文件内, -f filename 则可以执行 filename 内的 sed 动作;
#-r :sed 的动作支持的是延伸型正则表达式的语法。(默认是基础正则表达式语法)
#-i :直接修改读取的文件内容,而不是由屏幕输出。

#动作说明: [n1[,n2]]function
#n1, n2 :不见得会存在,一般代表“选择进行动作的行数”,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则“ 10,20[动作行为] ”

#function 有下面这些咚咚:
#a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
#c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
#d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
#i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
#p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
#s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正则表达式!例如 1,20s/old/new/g 就是啦!
  • 例
1
2
3
4
5
6
7
8
#删除2-5行的内容
nl /etc/passwd | sed '2,5d'
#在第二行后添加‘drink tea?’
nl /etc/passwd &#124; sed '2a drink tea'
#将2-5行的内容取代为‘No 2-5 number’
nl /etc/passwd | sed '2,5c No 2-5 number'
#仅列出 /etc/passwd 文件内的第 5-7 行
nl /etc/passwd | sed -n '5,7p'

sed 不仅可以进行行的处理,还可以进行部分数据的搜索并取代的功能。

1
2
3
4
#使用方法
sed 's/要被取代的字串/新的字串/g'
#
/sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'

vim的使用

发表于 2019-02-28 | Edited on 2025-04-24

vim 一共分三种模式:

  • 命令模式(command mode)
  • 编辑模式(insert mode)
  • 命令行模式(command-line mode)

命令模式又被称为一般模式,进入 vim 时默认为命令模式。命令模式下按 i 可进入编辑模式,编辑模式下按 Esc 可进入命令模式。命令模式下输入“:”能进入命令行模式,命令行模式也能进入命令模式。但是命令行模式和编辑模式不能直接切换,必须经过命令模式才可。

每个模式都有其特有的命令和功能。

  • 命令模式可以做浏览,删除等操作
  • 编辑模式可以输入内容
  • 命令行模式可以保持,退出等操作
命令模式(command mode)
移动光标
命令 解释
h(或 left) 左移动
j(或 down) 下移动
k(或 top) 上移动
l(或 right) 下移动
n(数字)+h \ j \ k \ l 移动 n 个位置
PageDown 或^f 向下移一页
PageUp 或^b 向上移一页
^d 向上移半页
^u 向上移半页
+ 光标移动到非空白字符的下一列头部
- 光标移动到非空白字符的上一列头部
Home 或 0 到列的头部
End 或\$ 到列的尾部
^Home 到文件头部
^End 到文件尾部
H 到屏幕的首行
M 到屏幕的中间行
L 到屏幕的尾行
G 到文件的尾行类似^End
nG 到文件的第 n 行
gg 到文件的头部类似^Home
区块选择
命令 解释
v 以字符为单位选择区块
V 以列为单位选择区块
^v 以矩形的形式选择区块
y 复制区块
p 粘贴区块
d 删除区块
搜索与取代
命令 解释
/word 向下查找
?word 向上查找
n 下一个
N 前一个
:n1,n2s/word1/word2/g 将 n1 列到 n2 列的 word1 替换为 word2
:1,\$s/word1/word2/g 将第一列到最后一列的 word1 替换为 word2
:1,\$s/word1/word2/gc 将第一列到最后一列的 word1 替换为 word2,并提示使用者是否替代
删除、复制、粘贴、撤销、恢复
命令 解释
x 向后删除,类似编辑模式的 Del
X 向前删除,类似编辑模式的 backspace
nx \ nX n 为数字,表示删除 n 个
dd 删除光标所在行
ndd 删除光标所在行开始的 n 行
d1G 删除光标所在行到文件第一行间的所有内容
dG 删除光标所在行到文件最后一行间的所有内容
d\$ 删除光标到改行结尾间的内容
d0 删除光标到改行头部间的内容
yy 复制光标所在行
nyy 复制光标所在行开始的 n 行
y1G 复制光标所在行到文件第一行间的内容
yG 复制光标所在行到文件最后一行间的内容
y\$ 复制光标到行尾间的内容
y0 复制光标到行头间的内容
p(小写) 粘贴,如果复制的内容是一列,将粘贴到光标所在含的下一行
P(大写) 粘贴,如果复制的内容是一列,将粘贴到光标所在含的上一行
u 撤销
^r 恢复
. 重复刚才的操作
编辑模式(insert mode)
命令 解释
i 进入编辑模式,并在光标处开始编辑
I 进入编辑模式,并在光标所在行头部开始编辑
a 进入编辑模式,并在光标处的后一个位置开始编辑
A 进入编辑模式,并在光标所在尾部开始编辑
o 进入编辑模式,并在光标所在行的下一行开始编辑
O 进入编辑模式,并在光标所在行的上一行开始编辑
r 进入替换模式,替换当前光标上的那个字
R 进入替换模式,可一直替换,直到按下 Esc
命令行模式(command-line mode)
命令 解释
:w 将编辑的数据写入硬盘
:w! 强行写入硬盘,即使没有写入权限。前提是当前的用户有修改权限的能力
:q 离开
:q! 强制离开,不保存
:wq 保存后离开
:wq! 强制保存后离开
:wq! 强制保存后离开
ZZ 若文件没改动就不储存离开,如果有改动就储存再离开
:! command 可以输入命令行界面的命令
:w [filename] 将文件另存为
:n1,n2 w [filename] 将文件 n1 到 n2 行间的内容另存为
:r [filename] 读取其他文件并插入到当前光标的下一行
:set nu 设置行号
:set nonu 取消行号
多文件编辑功能

当用 vim file1 file2 …同时打开多个文件时,可使用多文件编辑功能。

命令 解释
:files 查看当前这个 vim 打开的所有文件
:n 查看下一个文件
:N 查看前一个文件
多窗口功能

多窗口功能非常的方便,能让我买在同一 bash 中并列查看多个文件。

命令 解释
:sp [filename] 在新窗口中打开文件,如果不加名字就将当前文件在新窗口中打开
^w+down 切到下一个窗口
^w+up 切到上一个窗口
文字补全功能

例如在 htm 的 style 标签内些 css 时忘记了背景颜色的 css 名字,就可以用文字补全功能了。

组合按钮 补齐的内容
^x->^n 通过目前正在编辑的这个“文件的内容文字”作为关键字,予以补齐
^x->^f 以当前目录内的“文件名”作为关键字,予以补齐
^x->^o 以扩展名作为语法补充,以 vim 内置的关键字,予以补齐

Linux文件权限

发表于 2019-02-28 | Edited on 2025-04-24

Linux 下文件与文件夹的各个权限对应的意义是不同的,这篇文章主要写出文件与文件夹的可读、可写、可执行权限对应的功能。

文件

1.可读

可以读取文件内的文本,二进制码等文件的内容,前提是能进入该文件所在的文件夹。

2.可写

可以添加,删除,修改文件的内容,前提是能进入该文件所在的文件夹。

3.可执行

可以执行该文件,前提是能进入该文件所在的文件夹。

文件夹

1.可读

可以用 ls 列出文件的名称。

2.可读

可以对在文件夹下添加、删除文件和文件夹,还能修改文件或文件夹的名字。

3.可执行

可以进入该文件夹。

综合

1.ll 不能列出详细信息,只能看到文件名

当文件夹对当前用户只有可读权限时,使用”ll”命令列出该文件下所有文件的详细信息时,除了文件名外都是问号。文件夹添加当前用户的可执行权限即可看到全部详细。

Shell

发表于 2019-02-28 | Edited on 2025-04-24

什么是 shell

为了避免非专业人士直接操作系统导致系统受破坏,因此需要有个 shell 来操作。每个命令都是一个应用,shell 是一个能调用各个应用接口的程序。

shell 的命令

可以用 type 命令来查看那些命令是 shell 内置

1
type cd #cd是shell内置的

如果命令过长,可以用‘\’将回车转义(escape),这样就可以在下一行继续输入命令。

有时候需要删除和编辑写好的命令,可以通过下面的组合键操作。

组合键 功能
^u 删除光标处到头部的内容
^k 删除光标处到尾部的内容
^a 到命令头部
^e 到命令尾部

shell 的变量功能

获得变量

1
2
3
#shell中获取变量需要在变量前加$
#打印HOME变量
echo $HOME

设置变量

1
2
3
4
#用法
variable=value
#设置变量myname的值为Allen
myname=Allen

删除变量

1
unset variable
变量的设置规则
  • 变量名由字母、数字、下划线组成,并且不能由数字开头
  • 变量赋值等号左右两侧不能由空格,如“name = allenbai”或“name=allen bai”
  • 如果变量的值内要包含空格需要用‘“’或‘’’来包裹,或‘\’转义。如“name=”hello world””,“name=hello\ world”。注意如果使用的是单引号,那么单引号的内容就会变成一般的字符,里面定义的变量就会失去意义。如“name=’${name} is you’”,这里的“${name}”会变成字符,而不是变量。
  • 如果在赋值的过程中引用变量需要“”$变量名””或“${变量名}”。如“name=”$name”love”或“name=${name}love”
  • 如果需要其他指令提供的信息,可以用反单引号“`指令`”或“$(指令)”。如“name=$(uname -r)”
  • “export variable”可以将变量导到环境变量中。可以用“env”查看所有环境变量
  • 系统的变量默认全大写,自己的变量可以用小写,以便区分。这点要不要遵守看个人偏好。
  • “unset varaible”可以取消变量
范例
1
2
3
4
5
6
#进入到目前的核心目录模块
cd /lib/modules/`uname -r`/kernal
cd /lib/modules/$(uname -r)/kernal
#列出crontab相关文件的权限
ls -ld `locate crontab`
ls -ld $(locate crontab)
环境变量

查看当前环境变量

1
2
#查看当前环境变量
env

export 不加参数也可以查看当前环境的变量

1
export

如果需要查看包括自定义变量和环境变量在内的所有变量可以用 set

1
2
3
#不带任何参数即可
#貌似我安装的CentOS 7内无法显示变量
set

一些需要了解的系统环境变量

  • HOME 代表使用者的主文件夹
  • SHELL 当前环境用的是哪个 shell
  • HISTSIZE 能被系统记录的历史指令的数量
  • MAIL 当前用户的邮件信箱文件
  • PATH 可执行文件搜索的路径
  • LANG 语系数据
  • RANDOM 随机数变量。值介于 0~32767 之间。如果要取得 0~9 之间可以这样:
1
declare -i number=$RANDOM*10/32768;echo $number

bash 不只有环境变量,还有一些 bash 操作接口有关的变量,以及自定义的变量。用 set 可以观察所有变量。

除了环境变量外,其他 bash 内的变量

  • PS1 提示字符的设置
  • \$ shell 的 PID
  • ? 关于上个指令的回传值,0 表示成功,如果上个指令执行报错,会回传错误代码
  • OSTYPE,HOSTTYPE,MACHTYPE 主机硬件

环境变量与自订变量的差别是该变量是否会被子程序继续引用。

语系变量
1
locale
变量的有效范围

鸟哥书里提到的环境变量和自订变量也可以称为全局变量(global variable)和局部变量(local variable)。

read,array,declare

read 可以读取来自键盘输入的变量

1
2
3
4
5
6
7
#用法
read [-pt] variable
#-p :后面可以接提示字符
#-t :后面可以接等待的“秒数”

#请用户输入用户名
read -p '请在30秒内输入用户名' -t 30 user_name

declare/typeset 设置变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
#用法
declare [-aixr] variable
#-a :将变量声明为数组array
#-i :将变量声明为整数integer
#-x :将变量声明为环境变量
#-r :将变量声明为readonly类型,改变量不可更改内容,也不可以unset

#以下两种变量声明结果不同
sum=100+300+50;echo $sum #结果是 100+300+50
declare -i sum=100+300+50;echo $sum #结果是 450

#将-改成+ 可以进行取消的操作
declare +x sum

定义数组

1
2
3
4
person[1]='allen'
person[2]='jack'
person[3]='jane'
echo "${person[1]},${person[2]},${person[3]}"
变量的删除、取代和替换

从前面开始删除变量中的某些内容

1
2
3
4
5
#用法 变量名+‘#’+匹配规则(pattern)
#这里的‘#’表示从最前面开始向右删,且删最短的那个
${variable#pattern}
#如果要删除最长的要写‘##’,有点像正则表达式内的贪婪匹配
${variable##pattern}
1
2
3
4
5
6
echo $PATH
#/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/allen/.local/bin:/home/allen/bin
#删除/usr/local/bin:这个串
${PATH#/*local/bin:}
#删除前面所有目录,保留最后一个。注意这里是‘##’,如果是‘#’的话,它只删掉第一个
${PATH##/*:}

从后面开始删除变量中的某些内容

1
2
3
4
#从后面开始向左删,且删最短的那个。用法与‘#’类似
${variable%pattern}
#同样的,如果要删尽可能多的内容,是‘%%’
${variable%%pattern}

替换内容

1
2
3
4
#用法 pattern是指匹配规则 new是新内容
${variable/pattern/new}
#全部替换
${variable//pattern/new}
1
2
3
4
#将匹配到的第一个sbin替换为大写的SBIN
${PATH/sbin/SBIN}
#如果要全部替换,第一个 ‘/’要写成‘//’
${PATH//sbin/SBIN}
变量的测试与内容替换

‘-’

1
2
# 如果old_var未定义 则new_var的值为content
new_var=${old_var-content}

有点像 js 里的管道符号

1
var new_var = old_var || content;
1
2
3
# 如果old_var未定义或为空值 则new_var的值为content
#注意这里多了个‘:’
new_var=${old_var:-content}

‘+’

1
2
# 如果old_var有定义(包括空值) 则new_var的值为content
new_var=${old_var+content}

有点像 js 里的‘&&’

1
var new_var = old_var && content;
1
2
3
# 如果old_var有定义且值不为空 则new_var的值为content
#注意这里多了个‘:’
new_var=${old_var:+content}

‘=’

1
2
# 这个效果和‘-’一样,但多了一个效果,就是content赋值给new_var的同时也会赋值给old_var
new_var=${old_var=content}
1
2
3
# 这个效果和‘:-’一样,但多了一个效果,就是content赋值给new_var的同时也会赋值给old_var
#注意这里多了个‘:’
new_var=${old_var:=content}

‘?’

1
2
# 如果old_var未定义 就会告知控制台warn的内容
new_var=${old_var?warn}
1
2
3
# 如果old_var未定义或空值 就会告知控制台warn的内容
#注意这里多了个‘:’
new_var=${old_var:?warn}

其实不难发现‘-’,‘=’,‘?’都是假设 old_var 未定义才生效的,如果需要 old_var 为空值也生效加个‘:’就好。

而‘+’是假设 old_var 已定义才生效(无论有没有值),如果需要 old_var 为空值不生效加个‘:’就好。

命令别名与历史命名

命令别名
1
2
3
4
5
6
7
8
9
10
# 查看所有命令别名
alias
# 生成命令别名
alias comand_name='comand'
#例子 命令别名是lm,指令是‘ls -al | more’
alias lm='ls -al | more'
# 删除命令别名
unalias comand_name
#例子 删除命令别名 lm
unalias lm

bash 的环境配置文件

重要的环境配置文件
配置文件 功能
/etc/issue bash 的开机画面
/etc/motd bash 登录后的提示信息
/etc/locale.conf 决定默认使用语系,由/etc/profile.d/lang.sh 调用进来
/etc/profile 登录 bash 时读取的文件,是系统整体的设置
/etc/profile.d/*.sh /etc/profile 运行时回去执行/etc/profile.d 下所有.sh 结尾的文件
/usr/share/bash-completion/completions/* Tab 的补全设置,由/etc/profile.d/bash_completion.sh 调用进来
~/.bash_profile 或 ~/.bash_login 或 ~/.profile 属于使用者个人的设置。其实只会读取其中的一个。最先读取~/.bash_profile,如果没有再读取下一个,以此类推
~/.bashrc 获取 no-login shell 时会读取该文件。还有~/.bash_profile 会读该文件
~/.bash_history 历史命令记录的地方
~/.bash_logout 登出的时候执行的文件
读入环境配置文件的指令
1
2
3
4
5
6
#用法
source 配置文件文件名
#例子 当修改了~/.bash_profile时需要登出再登录后才生效,为了让设置生效可以用source指令
source ~/.bash_profile
#soucrce指令也可以写成‘.’
. ~/.bash_profile

万用字符和特殊符号

万用字符

万用字符有点像正则匹配有点类似但有差别

符号 意义
* 代表 0 到无穷个任意字符,相当于正则的.*
? 随机的一个字符
[] 表示括号内的任意一个字符
[-] 字符编码范围,例如[0-9]表示 0 到 9 之间的所有字符
[^] 反向选择,例如[^abc]表示非 a,b,c 的字符
特殊符号
符号 意义
# 注释符号
\ 转义
\ 管线(pipe),分隔两个管线命令的界定
; 连续指令下达分隔符号
~ 使用者的主文件夹
\$ 取用变量前置字符:亦即是变量之前需要加的变量取代值
& 工作控制 (job control):将指令变成背景下工作
! 逻辑运算符,not 的意思
/ 目录符号:路径分隔的符号
>和>> 数据流重导向:输出导向,分别是“取代”与“累加”
\<和\<\< 数据流重导向:输入导向
‘ ‘ 单引号,不具有变量置换的功能 (\$ 变为纯文本)
“ “ 具有变量置换的功能! (\$ 可保留相关功能)
` 两个“`”中间为可以先执行的指令,亦可使用 \$( )
() 在中间为子 shell 的起始与结束
{ } 在中间为命令区块的组合!
数据流重导向

数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据, 给他传输到
其他的地方,例如文件或者是设备。

符号 意义
\< 标准输入,将某个文件代替键盘输入
\<\< 标准输入,以某个符号作为文件的结束标记
>或 1> 标准输出,覆盖原内容
>>或 1>> 标准输出,添加的原内容尾部
2> 标准错误输出,覆盖原内容
2>> 标准错误输出,添加的原内容的尾部

以下是例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#例 将ll列出的内容保存到~/rootfile内
ll / > ~/rootfile

#例 创建catfile文件并将~/.bashrc的内容给它
cat > catfile < ~/.bashrc

#原来cat不仅仅只有读文件的功能,不加参数还会读取键盘录入
#例 用cat命令新建catfile,并以键盘的录入内容作为该文件的内容。想要结束输入按^d。
cat > catfile

#例 用cat命令新建catfile,并以‘eof’做为结束标记。
cat > catfile << 'eof' #当你输入eof回车时,就和按^d的效果是一样的

#例 使用find指令时,遇到没有读取权限的文件会报错,通过2\>将错误导到/dev/null
find /home -name .bashrc 2> /dev/null #/dev/null相当于垃圾桶黑洞
#例 将正确和错误的数据都写入同一个文件
find /home -name .bashrc > list 2> &1
#或者这么写
find /home -name .bashrc &> list
;和&&和||

‘;’这个很好理解,相当于每个语句的结束符。有了它,就可以同时写个条指令了。

1
2
#例 关机前执行两次sync同步写入磁盘
sync;sync;shutdown -h now

&&前后连接两个指令,前一个指令报错就不执行后一个指令;||前后连接两个指令,前一个不报错就不执行后一个指令。

1
2
3
4
#非root用户执行find /root,$?返回错误代码,所这里的ls不执行
find /root && ls
#这里的ls会执行
find /root || ls
1
2
#我们要在tmp下面创建/abc/hehe,但我们不知道abc是否存在。于是我们可以用下面的方法。
ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
解析:

情况一,tmp 下没 abc 文件夹。

由于没有 abc 文件夹,那第一个指令回传值(\$?)不为 0,也就是说第一个指令报错了,于是会执行第二指令新建 abc 目录。第二个指令执行成功,于是会执行第三个指令创建 hehe 文件。

情况二,tmp 下有 abc 文件夹。
第一个指令成功,所以第二个指令不执行。注意了,这行语句并没有就此结束。第一个回传值会继续传递,由于第一个回传值是 0,所以第三个指令会执行。

结论:无论有没有 abc 目录,我们都能创建/tmp/abc/hehe 文件

管线符号(pipe)

‘|’就是管线符号了。如果数据流重导向是将指令的结果导到设备和文件或设备和文件的数据导到指令的话;那么 pipe 就是将前一个命令的数据输出做为后一个命令的输入。
使用管线符号需要注意两点:

  1. 管线符号后面的那个命令必须是管线命令。能接受前一个指令的数据成为 standard input 才是管线命令。
  2. 管线命令仅会处理 standard output,对于 standard error output 会被忽略。
管线符号的组合运用
  • cut

cut 可以将数据按我们的要求切出来,可以想象成 split。该命令是按行来操作的。

1
2
3
4
5
6
7
8
#用法一;按分隔字符将一行数据切成多段,并攫取其中的几个片段
cut -d '分隔字符' -f 分段区间
#例 将$PATH按‘:’分隔,并取第一段和第4段
echo ${PATH} | cut -d ':' -f 1,4
#例 将$PATH按‘:’分隔,并取第4段到最后一段
echo ${PATH} | cut -d ':' -f 4-
#例 用last查看登录者信息,只显示登陆者名称
last | cut -d ' ' -f 1
1
2
3
4
#用法二;
cut -c 字符区间
#例 将export的信息前面 declare -x的字去掉
export | cut -c 12-
  • grep

查询每一行,把含有我们想要的文字的句子列出来。grep 能配合正则使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
grep [-acinv] [--color=auto] '搜寻字符' filename
#选项与参数:
#-a :将 binary 文件以 text 文件的方式搜寻数据
#-c :计算找到 '搜寻字串' 的次数
#-i :忽略大小写的不同,所以大小写视为相同
#-n :顺便输出行号
#-v :反向选择,亦即显示出没有 '搜寻字串' 内容的那一行!
#--color=auto :可以将找到的关键字部分加上颜色的显示喔!

#例 将last中出现root的那行取出
last | grep 'root'
#例 取出 /etc/man_db.conf 内含 MANPATH 的那几行
grep --color=auto 'MANPATH' /etc/man_db.conf
  • sort

sort 很明显是用来排序的。排序的字符和语系有关,因此, 如果您需要排
序时,建议使用 LANG=C 来让语系统一,数据排序比较好一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#用法
sort [-fbMnrtuk] [file or stdin]
#选项与参数:
#-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
#-b :忽略最前面的空白字符部分;
#-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
#-n :使用“纯数字”进行排序(默认是以文字体态来排序的);
#-r :反向排序;
#-u :就是 uniq ,相同的数据中,仅出现一行代表;
#-t :分隔符号,默认是用 [tab] 键来分隔;
#-k :以那个区间 (field) 来进行排序的意思

#例 将/etc/passwd下的账号进行排序
cat /etc/passwd | sort

#例 /etc/passwd内容是以‘:’分隔的,我们想用第三栏来排序
cat /etc/passwd | sort -t ':' -k 3

#取出last中的所有账号,并排序
last | cut -d ' ' -f1 | sort
  • uniq

将排序完的数据去重。(只有连续的重复内容才会被去除,所以使用前需要排序)

1
2
3
4
5
6
7
8
9
10
#用法
uniq [-ic]
#选项与参数:
#-i :忽略大小写字符的不同;
#-c :进行计数

#例 将last取出的账号排序并除重
last | cut -d ' ' -f1 | sort | uniq
#例 如果要在上个例子的基础上加上计数
last | cut -d ' ' -f1 | sort | uniq -c
  • wc

查看文件多少行,多少个字符

1
2
3
4
5
6
7
8
9
#用法
wc [-lwm]
#选项与参数:
#-l :仅列出行;
#-w :仅列出多少字(英文单字);
#-m :多少字符;

#查看/etc/man_db.conf里面有多少岁字、行、字符
cat /etc/man_db.conf | wc
  • tee

我们可以使用数据流重导向‘>’将 stdout 输入到文件。如果我们在输入到文件的同时,还要继续使用这个 stdout 的话,我们可使用 tee(双向重导向)。

1
2
3
4
5
6
7
#用法
tee [-a] file
#选项与参数:
#-a :以累加 (append) 的方式,将数据加入 file 当中!

#例 将last输出一份到last.list
last | tee last.list | cut -d " " -f1
  • unix2dos dos2unix

unix2dos 是将文本的 unix 换行符号换成 dos 的。

  • tr

删除或替换文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#用法
tr [-ds] SET1 ...
#选项与参数:
#-d :删除讯息当中的 SET1 这个字串;
#-s :取代掉重复的字符!

#例 将last输出的信息中所有的小写变成大写
last | tr [a-z] [A-Z]
#例 将/etc/passwd输出的小写中的冒号‘:’删除
cat /etc/passwd | tr -d ':'
#例 将/etc/passd转存成dos断行到/root/passwd中,再将^M符号删除
cp /etc/passwd ~/passwd && unix2dos ~/passwd
#例 上面那个方法可以用tr的方法来代替。^M可以用\r来代替
cat ~/passwd | tr -d '\r' > ~/passwd.linux
  • col

将 tab 转成对等的空白键

1
2
3
4
5
6
7
#用法
col [-xb]

#cat -A可以看到tab的符号为^I
cat -A /etc/man_db.conf
#将/etc/man_db.conf的tab转为空白键后再cat -A查看
cat /etc/man_db.conf | col -x | cat -A | more
  • join

将含有相同数据的行拼接在一起。使用时最好排序下。

1
2
3
4
5
6
7
8
9
10
11
12
13
#用法
join [-ti12]
#选项与参数:
#-t :join 默认以空白字符分隔数据,并且比对“第一个字段”的数据,
#如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
#-i :忽略大小写的差异;
#-1 :这个是数字的 1 ,代表“第一个文件要用那个字段来分析”的意思;
#-2 :代表“第二个文件要用那个字段来分析”的意思。

#例 用root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
join -t ':' /etc/passwd /etc/shadow | head -n 3
#例 :我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在/etc/group 当中的第三个字段,请问如何将两个文件整合?
join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
  • paste

paste 比 join 简单粗暴,它直接将两个文件的行相连,并用 tab 隔开

1
2
3
4
5
#用法
paste [-d] file1 file2
#选项与参数:
#-d :后面可以接分隔字符。默认是以 [tab] 来分隔的!
#- :如果 file 部分写成 - ,表示来自 standard input 的数据的意思。
  • expand

将 tab 转成空白键

1
2
3
4
5
#用法
expand [-t] file
#选项与参数:
#-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空白键取代。
#我们也可以自行定义一个 [tab] 按键代表多少个字符呢!#
  • split

他可以帮你将一个大文件,依据文件大小或行数来分区,就可以将大文件分区成为小文件了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#用法
split [-bl] file PREFIX
#选项与参数:
#-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
#-l :以行数来进行分区。
#PREFIX :代表前置字符的意思,可作为分区文件的前导文字。

#例 我的 /etc/services 有六百多K,若想要分成 300K 一个文件时?
cd /tmp; split -b 300k /etc/services services
#例 将上面分割出来的三个文件合成一个文件,名为servicesback
cat services* >> servicesback

#例 使用ls -al / 输出的信息中,没十行记录成一个文件
ls -al | split -l 10 - lsroot
  • xargs

xargs 可以读入 stdin 的数据,并且以空白字符或断行字符作为分辨,将 stdin 的数据分隔成为 arguments

1
2
3
4
5
6
7
8
9
xargs [-0epn] command
#选项与参数:
#-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空白键等等字符时,这个 -0 参数可以将他还原成一般字符。这个参数可以用于特殊状态喔!
#-e :这个是 EOF (end of file) 的意思。后面可以接一个字串,当 xargs分析到这个字串时,就会停止继续工作!
#-p :在执行每个指令的 argument 时,都会询问使用者的意思;
#-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。当 xargs后面没有接任何的指令时,默认是以 echo 来进行输出喔!

#例 取出/etc/passwd中的前三个用户,并依次对每个用户执行id命令
cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
  • -

在管线命令的使用中‘-’是非常重要的,它指代前一个命令的标准输出

1
2
mkdir /tmp/homeback
tar -cvf - /home | tar -xvf - -C /tmp/homeback

Linux常用命令

发表于 2019-02-28 | Edited on 2025-04-24

以下是阅读鸟哥的 Linux 私房菜第四版做的笔记。

一、Linux 入门

1. man 命令

虽然绝大多数数的命令带有–help 选项,但是想查看某命令的详细信息还是 man 比较详细。

1
2
3
4
man man #查看man命令的manual
man 1 man #查看id为1的man命令
man -f man #查看命令名中包含man字样的所有命令
man -k man #查看命令简介中包含man字样的所有命令
2.info 命令

虽然 man 命令很强大,但是一大段的文字在 bash 中展现出来难免难以查阅。info 命令将 man 的内容拆成多个 node 可以让使用者像操作 html 一样来查看命令的使用详情。

1
info info #查看info命令的.info文件

进入 info 的使用界面,为了方便操作,info 的使用界面带有很多快捷键。

1
2
3
4
5
6
7
8
9
10
11
12
n #Next 跳到下一个Node
p #prev 跳到上一个Node
] #下一节点,与n不同的是,n会略过子节点直接跳到下一个统计节点,]不会
[ #上一节点,效果和]差不多
u #Up 上一节点
b 或 Home #Begining 会到节点顶部
e 或 End #End 回到节点底部
空格 或 PgDn #向下移动一个屏幕高度,相当翻页
回车 会 Del 或PgUp #向上移动一个屏幕高度
Tab #跳到下一个最近的链接
m #链接跳转,需要input 链接的名字
? #查看更多的操作快捷键

二、文件权限

1.改变文件属性和权限
1
2
3
chgrp #change group
chown #change owner
chmod # change mode
1.改变文件所属组

Linux 的所有组的信息保留在/etc/group 文件内。该文件内的内容格式为:

1
2
#组名:密码:组id:组内所有成员
group_name:passwd:GID:user_list

为了安全,密码实际显示的是”X”,真正的密码保存在了/etc/shadow 内(该文件只有 root 才能打开)。

修改文件所属组方法如下

1
2
#用法
chgrp [options] group file ...
2.改变文件所属用户

linux 的所有用户信息保留在/etc/passwd 文件内。改文件内的内容格式为:

1
2
#用户名:密码:用户id:用户的主要组id:可选字段,通常为了存放信息:home目录所在地址:shell
account:password:UID:GID:GECOS:directory:shell

同样,这里的密码显示的是’x’.

修改文件所属用户的方法如下

1
2
3
4
5
6
7
8
#用法
chown [options] user[:group] file ...
#修改index.html所属用户为allen
chown allen index.html
#修改index.html所属组为root
chown :root index.html
#递归修改index.html所属组为root所属用户为allen
chown -R allen:root index.html
3.改变文件权限

修改文件的权限有两种方法,数字和符号

  1. 数字

r:4 w:2 x:1,根据需要,将权限对应的数字相加即可得到一个数字。

1
2
3
4
#用法
chmod [options] mode file ...
#将index.html 的权限改为rwxr-xr--
chmod 754 index.html
  1. 符号

a:所有对象 u:所属用户 g:所属组 o:其他人,根据需要,将对应的权限赋值给对应的对象就好

1
2
3
4
#用法
chmod [options] mode file ...
#将index.html 的权限改为rwxrwxrwx
chmod a=rwx index.html

如果想去掉或添加某个对象的某个权限,可以用’-‘和’+’

1
2
3
4
#index.html的权限为rwxrwxrwx,现在想去掉其他用户的可执行权限
chmod o-x index.html
#index.html的权限为rwxrwxrw-,现在想添加其他用户的可执行权限
chmod o+x index.html
  1. 隐藏权限

在老的 Ext 文件系统上,可以添加与查看隐藏权限,在 xfs 文件系统(CentOS 7 默认)内仅支持部分参数

1
2
chattr #添加隐藏权限
isattr #查看隐藏权限
  1. 默认权限 umask

用户创建文件和文件是都会带有默认的权限。

1
2
umask #查看默认权限 4位数
umask -S #查看默认权限 标识符的形式

一般用户的默认 umask 是 0002,要如何解读这 4 位数呢。第一位是特殊权限的位置,后三位就是我们平时理解的 user group others。但是有所不同的是显示的数字是表示要排除的特权。

最后一位是 2,那表是要排除掉可写权限。于是 0002 对于新创建的文件,它的默认权限是-rw-rw-r;对于新创建的目录,它的默认权限是-rwxrwxr-x。

注意这里的文件和目录的默认权限有所不同,文件本身就默认没有可执行权限的,而目录默认是有可执行权限。因为对大部分文件来说,可执行权限没有意义,但对于目录来说就很有必要。

root 用户的默认 umask 是 0022,这是为了安全考虑,这样 root 创建的文件或目录,在默认情况下,其同组成员是无法修改的。

  1. 特殊权限

SUID SGID TBIT 是三个特殊权限。

有时候会看到 rwsr-xr-x 的权限,这里的 s 就是 SUID。该权限的作用就是其他用户在执行的时候可以获得该文件所属用户的权限(注意仅在文件执行期间)。,SUID 仅可用在 binary program 上, 不能够用在 shell script 上面!以下是设置方法。

1
2
#将权限为 rwxr-xr-x的test修改为rwsr-xr-x
chmod 4755 test

有时候会看到 rwx–s–x 的权限,这里的 s 就是 SUID。其作用类似 SUID,就是在文件的执行期间其他用户可以获得该程序群组的支持。

SUID 同时也可以用在目录上,其作用就是使用者进入该目录时,它的有效群组会变为该目录的群组。同时,使用者在该目录下创建的文件群组和该目录的群组相同。

以下是设置方法。

1
2
#将权限为 rwxr-xr-x的test修改为rwxr-sr-x
chmod 2755 test

查看/tmp 的权限是,会看到 drwxrwxrwt,这个 t,这里的 t 就是 TBIT。它可以让使用者在它下面创建的文件仅有使用者自己和 root 能删除。

以下是设置方法。

1
2
#将权限为 rwxr-xr-x的test修改为rwxr-xr-t
chmod 1755 test

其实权限设置是有 4 位的,第一位表示的是特殊权限。SUID:4,SGID:2,TBIT:1。把他们相应的数字相加就能组合使用。注意如果出现大写的 S 或 T,那说明这是无效的特殊权限,因为其所在的位置没有设置可执行权限。

三、文件与目录管理

1.路径与文件、目录管理
1.路径
1
2
cd #变换目录
pwd #显示当前的路径

特殊的目录一览

1
2
3
4
5
. #代表此层目录
.. #代表上一层目录
- #代表前一个工作目录
~ #代表“目前使用者身份”所在的主文件夹
~account #代表 account 这个使用者的主文件夹(account是个帐号名称)
2.文件、目录管理
1
2
3
4
5
6
7
8
mkdir #创建文件夹
rmdir #移除文件夹
touch #创建文件,也可以用来刷新文件的mtime ctime atime
ls #查看目录下的文件
rm #删除文件
mv #移动文件 可以替代rename的功能
cp #拷贝
file #查看文件类型 如sticky directory
2.文件内容查看

查看文件内容可以用各种编辑器例如 nano、vim 等。在 X window 下还可以用 gedit 编辑器。

如果仅仅只是查看文件,用编辑器就有点小题大做。linux 有直接查看文件的命令

1
2
3
4
5
6
7
8
cat #查看文件
tac #从文件尾部开始查看文件,与cat相反
nl #带行号的文件查看
more #带翻页的查看
less #也是带翻页的查看,比more强大,man page默认的就是less
head #查看文件头部
tail #查看文件尾部,通常加个-f选项可以watch文件的变化,可用来查看logger文件
od #查看二进制文件
3.指令文件名的搜索
1
2
which #查看指令的可执行文件所在目录,它是按PATH的所规范的路径寻找
type #which查不到的,可能是bash自带的指令,此时就可以用type来查了
4.文件的查找
1
2
3
4
find #强大的查找功能,但比较消耗性能和时间
whereis #由于只搜索某几个目录,所以比find速度快
locate #通过数据库查询,所以比较快。但是有时候,有些文件还没更新到数据库内,会导致查不到。
updatedb #手动更新数据库 方便locate查找

四、磁盘与文件系统管理

1.查看文件系统的 superblock

ext 文件系统

1
dumpe2fs [options] 设备文件名

xfs 文件系统

1
xfs_info 挂载点 | 设备文件名
2.查看磁盘或目录的容量
1
2
df #列出文件系统的整体磁盘使用量
du #评估文件系统的使用量
1
2
3
4
5
6
7
8
#用法
df [options] [目录或文件名]
#将系统内所有的filesystem列出来
df -h
#查看/etc目录的可用的磁盘容量列出
df -h /etc
#将目前各个partition当中可用的inode数量列出
df -ih
1
2
3
4
5
6
#用法
du [options] [目录或文件名]
#列出目前目录下的所有目录的大小
du
#列出目前目录下的所有文件和目录的大小
du -a
3.创建连接
1
2
3
#用法
ln [-sf] 来源文件 目标文件
-s #不带该参数,默认是创建实体连接(hard link)加上是创建符号连接(symbol link)
4.查看磁盘分区情况
1
2
#查看磁盘分区情况
lsblk [options] [device]
1
2
#查看分区uuid
blkid
1
2
#列出磁盘的分区表类型与分区信息
parted device_name print
5.磁盘分区:gdisk/fdisk

注意 MBR 分区表用 fdisk;GPT 分区表用 gdisk。

1
gdisk 设备名

分区完毕并写入后,分区表并没有更新。可以通过重启来更新,或者 partprobe 更新 linux 核心的分区表信息。

1
partprobe [-s]

我们可以用 lsblk 来查看分区表的姿态,也可以:

1
cat /proc/partitions #核心的分区纪录
5.磁盘格式化

make filesysytem 创建文件系统即是格式化。

这部分理解比较吃力,请查阅鸟哥的 linux 382 页

格式化为 xfs 文件系统

1
mkfs.xfs  [-b bsize] [-d parms] [-i parms] [-l parms] [-L label] [-f] [-r parms] 设备名

格式化为 ext4 文件系统

1
mkfs.ext4 [-b size] [-L label] 设备名称

格式化为其他文件系统

1
2
3
mkfs
#查看支持的文件系统
mkfs[tab][tab]
6.文件系统挽救

每个文件系统都有其对应的指令

xfs

1
xfs_repair [-fnd] 设备名称

ext4。fsck.ext4 这是个综合指令

1
fsck.ext4 [-pf] [-b superblock] 设备名称
7.文件系统挂载与卸载

进行挂载前要确定好几件事:

  • 单一文件系统不应该被重复挂载在不同的挂载点(目录)中;
  • 单一目录不应该重复挂载多个文件系统;
  • 要作为挂载点的目录,理论上应该都是空目录才是。
1
mount #比较复杂 请用man来查看
1
2
3
4
5
#鸟哥现在比较推荐UUID的方式来挂载,应为UUID是唯一标识符,不会发生重复
#挂载前用blkid查看要挂载的分区的UUID
blkid
#然后通过UUID进行挂载到对应的目录,注意该目录必须要存在
mount UUID='分区的UUID' 目录

有挂载就有卸载

1
umount [-fn] 设备文件名或挂载点
7.磁盘/文件系统参数修订
1
2
3
mknod
xfs_admin #修改 XFS 文件系统的 UUID 与 Label name
tune2fs #修改 ext4 的 label name 与 UUID

一个有趣的命令 uuidgen

1
uuidgen #生成一个新的uuid
8.开机挂载 /etc/fstab 及 /etc/mtab

修改/etc/fstab 即可开机自动挂载

五、文件与文件系统的压缩,打包与备份

1.文件压缩

压缩指令 gzip,bzip2,xz

1.gzip/zcat/zmore/zless/zgrep

gzip 同时可以解压 compress zip gzip 文件。

1
2
3
4
5
6
7
8
#用法
gizp [options] 压缩文件
#压缩文件,并查看压缩比
gizp -v services
#保留原文件的文件名,并且创建压缩文件!
gizp -c services > services.gz
#解压
gizp -d services.gz

zcat/zmore/zless/zgrep 分别对应 cat/more/less/grep。用来直接查看压缩文件内容。

1
2
3
4
#查看services.gz的内容
zcat services.gz
#查看压缩文件内带'http'字样的文本
zgrep -n 'http' services.gz

压缩的时候配合上 time 命令,还可以了解压缩用的时间。

1
time gzip services
2.bzip2/bzcat/bzmore/bzless/bzgrep

用法和 gizp 相同,更多信息可以 man 它。它的优点是压缩率比 gzip 更高,同时花的时间也会多一些。

3.xz/xzcat/xzmore/xzless/xzgrep

用法和 gizp/bzip2 相同,它的压缩比例比 bzip2 还要高,因此比较花费时间。

它还提供了一些更方便的 option

1
2
3
4
#列出压缩文件详细息信息
xz -l services.xz
#保留原文件的文件名,并且创建压缩文件!
xz -k services
2.打包指令

打包命令有点复杂,以下列出了常用的简单命令。高级用法请自行 man 它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#压缩 将z改成j可以用bzip2压缩,改成J可以用xz压缩
tar -zcv -f filename.tar.gz 要被压缩的文件或目录名称(可以多个,用空格隔开)
#查看压缩包
tar -ztv -f filename.tar.gz
#解压
tar -zxv -f filename.tar.gz [要解开的某个文件] -C 欲解压缩到的目录
#参数说明
-z 通过gzip的支持进行压缩/解压
-j 通过bzip2的支持进行压缩/解压
-J 通过xz的支持进行压缩/解压
-c 创建打包文件
-t 查看打包文件
-x 解开压缩包
-v 列出压缩包内的文件
-f 后面要立刻接要被处理的文件名!建议 -f 单独写一个选项啰!(比较不会忘记)
-C 这个选项用在解压缩,若要在特定目录解压缩,可以使用这个选项。

-p 保留备份数据的原本权限与属性,常用于备份(-c)重要的配置文件
-P 保留绝对路径,亦即允许备份数据中含有根目录存在之意;

tar 可以用来备份文件

1
2
3
#备份/etc目录,这里的time命令只是为了查看时间开销
#p是为了保留原本的权限与属性。
time tar -jcpv -f /root/etc.bar.bz2 /etc

tar 可以取出压缩包内的某个文件

1
2
3
4
5
6
#以上面备份好的文件为例
#我们先查看压缩包,找到要取出的文件
tar -jt /root/etc.bar.bz2 | grep 'shadow'
#发现要找的文件目录为 etc/shadow
#然后用下面的方法取出
tar -jxv /root/etc.bar.bz2 etc/shadow

tar 打包的时候可以将不需要的文件排除在外

1
tar -jcv -f /root/system.tar.bz2 --exclude=/root/etc* --exclude=/root/sysytem.tar.bz2 /etc /root

tar 可以仅备份比某个时刻要新的文件

1
2
3
4
5
6
#查找比/etc/passwd的变更日期还新的文件
find /etc --newer /etc/passwd
#如果我们只备份比/etc/passwd新的内容,需要先知道/etc/passwd更变日期
ll /etc/passwd
#/etc/passwd变更日期为2018/5/5
bar -jcv -f /root/etc.newer.than.passwd.tar.bz2 --newer-mtime="2018/05/05" /etc/*
3.XFS 文件系统的备份与还原

使用 tar 通常是针对目录系统来进行备份的工作,如果想针对整个 xfs 文件系统进行备份需要 xfsdump。

还原的话用 xfsrestore。

该部分内容比较麻烦,请看鸟哥第四版 8.5 部分。

拷贝还可以用 dd。dd 该方法是一个扇区一个扇区拷的。

1
dd -if input_file -of output_file -bs 每个block的大小(默认一个扇区的大小) count 多少个block
12
博主

博主

记录学习的过程

18 日志
3 标签
© 2025 Allen Bai
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Pisces v7.0.0