本文为译文,原文链接:shell
shell
是一个高效的、文本化的计算机接口。
shell
提示符:当你打开终端时看到的一切。可以让用户执行的程序和命令,常见的有:
-
cd
改变目录 -
ls
列出文件和目录 -
mv
和cp
移动和复制文件
但是shell
允许您做更多的事情;您可以调用计算机上的任何程序,并且命令行工具的存在就是为了完成您可能想做的任何事情。他们往往比他们的图形对手更有效率。我们这门课会讲到很多。
shell
提供交互式编程语言 ("脚本")。有很多种shell
:
- 您可能用过
sh
或者bash
- 和语言相关的
shell
:csh
- 或者更好用的shell:
fish
,zsh
,ksh
在这个课堂上,我们将关注无处不在的sh
和bash
,但是使用其他的shell
感觉更好。我喜欢fish
。
在您的工具箱中,shell
程序是一个非常有用的工具。可以直接在提示符下编写程序,也可以将程序写入文件。
通过#!/bin/sh
+chmod +x
将shell程序变成可以执行的
使用shell
工作
将一个命令运行多次:
for i in $(seq 1 5); do echo hello; done
有很多东西可以展开来讲:
-
for x in list; do BODY; done
-
;
终止一个命令 -- 相当于换行 - 遍历
list
,将每个值赋值给x
,然后运行 - 分割标志符是“空格”,我们稍后会讲到
-
shell
中没有花括号,所以使用do
+done
-
-
$(seq 1 5)
- 运行
seq
命令,参数分别为1和5 - 使用括号内命令的输出替换 $()
- 相当于
for i in 1 2 3 4 5
- 运行
-
echo hello
-
shell
脚本中的所有内容都是命令 - 在本例中,运行
echo
命令,将打印该命令的参数hello
- 所有命令都可以在
$PATH
搜索到
-
我们可以举个例子:
for f in $(ls); do echo $f; done
将打印当前目录中的每个文件名。可以使用=
设置变量的值(=
两边不需要空格)
foo=bar
echo $foo
这里也有一些特殊的变量:
-
$1
-$9
:脚本的参数 -
$0
:脚本的名称 -
$#
:脚本的参数个数 -
$$
:当前脚本的进程ID
只打印目录
for f in $(ls); do if test -d $f; then echo dir $f; fi; done
这里展开来讲:
-
if CONDITION; then BODY; fi
-
CONDITION
是一个命令,如果返回时为0(success)
,就会执行BODY
- 也可以继续执行
else
或者elif
- 同样,没有花括号,所以使用
then
和fi
-
-
test
是另外一个命令,提供各式各样的检查与对比功能,退出时会返回对比结果,如果为真,则返回0($?)
-
man COMMAND
会对您有很大的帮助,比如:man test
- 也可以使用
[
+]
执行,比如:[ -d $f ]
- 查看一下
man test
和which [
的执行结果
- 查看一下
-
可是等等!结果是错误的!如果有个文件叫做“我的文档怎么办”?
-
for f in $(ls)
展开为for f in My Documents
- 先以
My
为test
的执行参数,然后以Documents
作为参数 - 这不是我们想要的!
-
shell
脚本中导致出现问题最多的原因
参数分割
Bash
是通过空格分割参数;但这并不总是您想要的!
- 需要使用引号处理
for f in "My Documents"
中f
的空格,才能正确的执行 - 其他地方也有同样的问题,您看到过吗?比如
test -d $f
:如果$f
中包含空格,test
将会发生错误! -
echo
碰巧没有问题,因为按空格分隔联接,但是如果文件名包含换行符,怎么办?!变成空格! - 引号用于所有不希望被拆分的参数
- 我们该如何修复上面的脚本呢?您认为
for f in "$(ls)"
怎么样?
答案是通配符!
- Bash知道如何使用模板查找文件:
-
*
任意字符串 -
?
任意字符 -
{a,b,c}
这些字符中的任意一个
-
-
for f in *
:这个文件夹下所有的文件 - 在使用通配符时,每个匹配的文件都将变成自己的参数
- 在使用时,仍需要确保引号的正确使用:
test -d "$f"
- 在使用时,仍需要确保引号的正确使用:
- 可以使用这些提高通配符效率:
-
for f in a*
: 当前文件夹下,所有以a
开头的文件 -
for f in foo/*.txt
:foo
文件夹下,所有以.txt
结尾的文件 -
for f in foo/*/p??.txt
: 在foo
的子文件夹下,以p
开头的三个字母的文件
-
空格的问题不止于此:
-
if [ $foo = "bar" ]; then
-- 看看这个问题? - 如果
$foo
是空的呢?[
的参数是=
和bar
... - 可以用
[ x$foo = "xbar" ]
来解决这个问题,但是效率低 - 相反,使用
[[
:一个bash
内置的具有特殊解析的比较器- 也可以使用
&&
代替-a
,-o
连接||
等等
- 也可以使用
可组合性
Shell之所以强大,部分原因在于它的可组合性。可以将多个程序链接在一起,而不是让一个程序做每一件事情。
关键字是|
。
a|b
表示同时运行a
和b
,将a
的所有输出,当作b
的输入,打印b
的输出。
您启动的所有程序(“进程”)都有三个“流”:
-
STDIN
:当程序读取输入时,它从这里开始 -
STDOUT
:当程序打印东西时,它就在这里 -
STDERR
:程序可以选择使用的第二个输出 - 默认的,
STDIN
是您的键盘输入,STDOUT
和STDERR
都是您的终端。但是您可以改变他们!-
a | b
将a
的输出当作b
的输入 - 同样还有:
-
a > foo
(将a
的标准输出写入foo
文件) -
a 2> foo
(将a
的标准错误输出写入foo
文件) -
a < foo
(a
的标准输入是从foo
文件读取的) - 提示:
tail -f
将打印文件内容,即使他正在被写入
-
-
为什么这个这么有用?您亲自试试下面程序的输出!
-
ls | grep foo
:包含单词foo
的所有文件 -
ps | grep foo
:包含单词foo
的所有进程 -
journalctl | grep -i intel | tail -n5
:最后5条带有intel(不区分大小写)的系统日志消息 -
who | sendmail -t me@example.com
:将登录用户列表发送到me@example.com
- 形成了许多数据处理的基础,稍后我们将讨论它
Bash还提供了许多其他编写程序的方法。
您可以组合形成一个命令(a; b) | tac
:先运行a
,然后运行b
,然后把他们的所有输出当作tac
命令的输入,tac
是一个将输入反序的命令。
一个不太为人所知但超级有用的方法是过程替换。b <(a)
将运行a,为输出流生成一个临时文件名,并将该文件名传递给b。举个例子:
diff <(journalctl -b -1 | head -n20) <(journalctl -b -2 | head -n20)
将向您展示前一个引导日志的前20行与更前一个引导日志的前20行之间的区别。
任务和进程控制
如果您在后台执行周期更长的任务呢?
- 在后台运行的程序是以
&
结尾- 它会立即给你提示
- 如果你想同时运行两个程序,比如服务器和客户端,这很好解决:
server & client
- 注意:正在运行的程序仍将终端设置为标准输出,试一试:
server > server.log & client
- 通过
jobs
查看所有的进程- 注意现实
Running
的
- 注意现实
- 使用
fg %JOB
将其放到前台(没有参数是最新的) - 如果您想将当前的程序放入后台:
^Z
+bg
(这里的^Z
代表按Ctrl+Z
)-
^Z
将当前的进程停止,并将它变成一个job
-
bg
将最新的job
在后台运行(就像使用了&
)
-
- 后台
jobs
仍然绑定到当前会话,如果注销,则退出。您可以使用disown
或者nohup
切断这种绑定关系。 -
$!
是最后一个后台进程的pid
在你的电脑上运行的其他东西呢?
-
ps
很好用:列出正在运行的进程-
ps -A
:打印所有用户的进程(也包括ps ax) -
ps
有很多参数:可以通过man ps
查看
-
-
pgrep
:搜索进程(和ps -A | grep
类似)-
pgrep -af
:通过参数搜索和显示
-
-
kill
:通过ID向进程发送信号(pkill by search + -f)- 信号告诉进程“做什么事”
- 最常见:
SIGKILL
(-9
或-KILL
):立刻退出,相当于^\
- 还有:
SIGTERM
(-15
or-TERM
):立刻优雅的退出,相当于^C
标志符
大多数命令行程序都使用标志符接受参数。标志符通常有短形式(-h
)和长形式(--help
)。通常运行CMD -h
或man CMD
会给你展示该CMD
可用的标识符的列表。短标志通常可以组合使用,运行rm -r -f
相当于运行rm -rf
或者rm -fr
。一些常见的标识符是有约定俗成的标准的,您会发现它们在很多命令中:
-
-a
一般指所有文件(也包括那些以点开头的) -
-f
通常指强制做什么事情,比如说rm -f
-
-h
大多数命令都是显示帮助 -
-v
通常启用详细输出 -
-V
通常打印命令的版本
此外,双破折号--
用于内置命令和许多其他命令中,表示命令选项的结束,之后只接受位置参数。因此,如果您有一个可以使用-v
参数的文件(文件类型支持使用),并且想要grep
它,grep pattern -- -v
可以,但是grep pattern -v
不行。事实上,创建这种文件的方法是touch -- -v
。
附录
title: 【译】Shell 入门教程
author: zhangpeng
date: 2019.02.17
网友评论