linuxshell.md 5.6 KB
Newer Older
L
labuladong 已提交
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
我个人很喜欢使用 Linux 系统,虽然说 Windows 的图形化界面做的确实比 Linux 好,但是对脚本的支持太差了。一开始有点不习惯命令行操作,但是熟悉了之后反而发现移动鼠标点点点才是浪费时间的罪魁祸首。。。

**那么对于 Linux 命令行,本文不是介绍某些命令的用法,而是说明一些简单却特别容易让人迷惑的细节问题**

1、标准输入和命令参数的区别。

2、在后台运行命令在退出终端后也全部退出了。

3、单引号和双引号表示字符串的区别。

4、有的命令和`sudo`一起用就 command not found。

### 一、标准输入和参数的区别

这个问题一定是最容易让人迷惑的,具体来说,就是搞不清什么时候用管道符`|`和文件重定向`>``<`,什么时候用变量`$`

比如说,我现在有个自动连接宽带的 shell 脚本`connect.sh`,存在我的家目录:

```shell
$ where connect.sh
/home/fdl/bin/connect.sh
```

如果我想删除这个脚本,而且想少敲几次键盘,应该怎么操作呢?我曾经这样尝试过:

```shell
$ where connect.sh | rm
```

实际上,这样操作是错误的,正确的做法应该是这样的:

```shell
$ rm $(where connect.sh)
```

前者试图将`where`的结果连接到`rm`的标准输入,后者试图将结果作为命令行参数传入。

**标准输入就是编程语言中诸如`scanf`或者`readline`这种命令;而参数是指程序的`main`函数传入的`args`字符数组**

前文「Linux文件描述符」说过,管道符和重定向符是将数据作为程序的标准输入,而`$(cmd)`是读取`cmd`命令输出的数据作为参数。

用刚才的例子说,`rm`命令源代码中肯定不接受标准输入,而是接收命令行参数,删除相应的文件。作为对比,`cat`命令是既接受标准输入,又接受命令行参数:

```shell
$ cat filename
...file text...

$ cat < filename
...file text...

$ echo 'hello world' | cat
hello world
```

**如果命令能够让终端阻塞,说明该命令接收标准输入,反之就是不接受**,比如你只运行`cat`命令不加任何参数,终端就会阻塞,等待你输入字符串并回显相同的字符串。

### 二、后台运行程序

比如说你远程登录到服务器上,运行一个 Django web 程序:

```shell
$ python manager.py runserver 0.0.0.0
Listening on 0.0.0.0:8080...
```

现在你可以通过服务器的 IP 地址测试 Django 服务,但是终端此时就阻塞了,你输入什么都不响应,除非输入 Ctrl-C 或者 Ctrl-/ 终止 python 进程。

可以在命令之后加一个`&`符号,这样命令行不会阻塞,可以响应你后续输入的命令,但是如果你退出服务器的登录,就不能访问该网页了。

如果你想在退出服务器之后仍然能够访问 web 服务,应该这样写命令 `(cmd &)`

```shell
$ (python manager.py runserver 0.0.0.0 &)
Listening on 0.0.0.0:8080...

$ logout
```

**底层原理是这样的**

每一个命令行终端都是一个 shell 进程,你在这个终端里执行的程序实际上都是这个 shell 进程分出来的子进程。正常情况下,shell 进程会阻塞,等待子进程退出才重新接收你输入的新的命令。加上`&`号,只是让 shell 进程不再阻塞,可以继续响应你的新命令。但是无论如何,你如果关掉了这个 shell 命令行端口,依附于它的所有子进程都会退出。

`(cmd &)`这样运行命令,则是将`cmd`命令挂到一个`systemd`系统守护进程名下,认`systemd`做爸爸,这样当你退出当前终端时,对于刚才的`cmd`命令就完全没有影响了。

类似的,还有一种后台运行常用的做法是这样:

```shell
Q
qhhndaye888 已提交
88
$ nohup some_cmd &
L
labuladong 已提交
89 90
```

Q
qhhndaye888 已提交
91
`nohup`命令也是类似的原理,不过通过我的测试,还是`(cmd &)`这种形式更加稳定。
L
labuladong 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

### 三、单引号和双引号的区别

不同的 shell 行为会有细微区别,但有一点是确定的,**对于`$`,`(`,`)`这几个符号,单引号包围的字符串不会做任何转义,双引号包围的字符串会转义**

shell 的行为可以测试,使用`set -x`命令,会开启 shell 的命令回显,你可以通过回显观察 shell 到底在执行什么命令:

![](../pictures/linuxshell/1.png)

可见 `echo $(cmd)``echo "$(cmd)"`,结果差不多,但是仍然有区别。注意观察,双引号转义完成的结果会自动增加单引号,而前者不会。

**也就是说,如果 `$` 读取出的参数字符串包含空格,应该用双引号括起来,否则就会出错**

### 四、sudo 找不到命令

有时候我们普通用户可以用的命令,用`sudo`加权限之后却报错 command not found:

```shell
$ connect.sh
network-manager: Permission denied

$ sudo connect.sh
sudo: command not found
```

原因在于,`connect.sh`这个脚本仅存在于该用户的环境变量中:

```shell
$ where connect.sh 
/home/fdl/bin/connect.sh
```

**当使用`sudo`时,系统认为是 root 用户在执行命令,所以会去搜索 root 用户的环境变量**,而这个脚本在 root 的环境变量目录中当然是找不到的。

C
Cherry 已提交
126
解决方法是使用脚本文件的绝对路径,而不是用脚本的相对路径:
L
labuladong 已提交
127 128 129

```shell
$ sudo /home/fdl/bin/connect.sh
L
test  
labuladong 已提交
130 131 132 133 134
```

坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:

![labuladong](../pictures/labuladong.jpg)
L
labuladong 已提交
135 136 137 138 139 140


[上一篇:一文看懂 session 和 cookie](../技术/session和cookie.md)

[下一篇:加密算法的前身今世](../技术/密码技术.md)

Q
qhhndaye888 已提交
141
[目录](../README.md#目录)