之前介绍了大量的命令,有基础操作相关的,也有问题诊断相关的,但Linux中还有一类命令,被时常使用在工作当中,如curl、mysql、ssh、vim等,本篇对它们做一个整体介绍。
curl是一个多功能的网络客户端命令,可以支持非常多的网络协议,如HTTP、FTP、IMAP、SMTP、SMB、LADP、MQTT等,但大部分情况下,我们将其作为HTTP协议的客户端工具来使用,用来调试HTTP接口,常见用法如下:
# 发送HTTP请求,默认GET方法
curl 'http://localhost/user/get'
# -d指定请求体,有-d参数后,curl会使用POST方法
# 如果不指定Content-Type,默认是application/x-www-form-urlencoded
curl 'http://localhost/user/add' -d 'id=32&name=zhangsan'
# --data-urlencode,将请求体做urlencode编码后再发送
curl 'http://localhost/user/add' --data-urlencode 'id=32&name=zhangsan'
# 用-H添加请求头,如下指定Content-Type是application/json
curl 'http://localhost/user/add' -H 'Content-Type: application/json; charset=UTF-8' -d '{"id":32,"name":"zhangsan"}'
# 使用-X,可以指定请求方法
curl -X DELETE 'http://localhost/user/delete'
curl -X PUT 'http://localhost/user/modify' -d 'id=32&name=zhangsan'
# -G,可以将请求体拼接到url,所以下面两种调用方法是等效的
curl -G 'http://localhost/user/add' -d 'id=32&name=zhangsan'
curl 'http://localhost/user/add?id=32&name=zhangsan'
# -i和-I可以显示响应头,区别是-i会同时显示响应头与内容,-I只显示响应头
curl -i 'http://localhost/user/add?id=32&name=zhangsan'
# 查看curl处理细节(非常常用)
curl -v http://www.baidu.com
# -s不显示进度提示信息与错误信息,-S显示错误信息
curl -sS http://www.baidu.com
# 使用time执行curl,只能看到请求整体响应时间
$ time curl "http://www.baidu.com" -o /dev/null -s
...
real 0m0.066s
user 0m0.010s
sys 0m0.000s
# 如下,通过-w可以打印请求详细耗时情况,包括dns查找耗时,连接建立耗时、收到服务端响应的首字节耗时、响应结果全部获取到的耗时,注意时间都是累加的,即相对于命令执行那一刻开始
$ curl -w " time_namelookup:%{time_namelookup}s\n time_connect:%{time_connect}s\n time_starttransfer:%{time_starttransfer}s\n time_total:%{time_total}s\n speed_download:%{speed_download}\n http_code:%{http_code}" "http://www.baidu.com" -o /dev/null -s
time_namelookup:0.009906s
time_connect:0.035826s
time_starttransfer:0.062370s
time_total:0.062406s
speed_download:38403.000
http_code:200
另外,curl也可以用来做文件上传与下载,如下:
# 下载文件,-L让curl支持重定向,-#显示进度条,-O响应体下载为文件
curl -LO -# http://localhost/user/avatar/xxxxx.png
# wget也经常用于下载文件
wget http://localhost/user/avatar/xxxxx.png
# 上传文件,-F类似于html里的form,使用-F后,Content-Type默认是multipart/form-data,这正是web上常见的文件上传方式
curl http://localhost/user/avatar/upload -F 'id=32' -F 'avatar=@./xxxxx.png'
# 上传文件,这也是一种上传文件的方式,请求体直接就是文件内容,整个传给服务器
curl http://localhost/user/avatar?id=32 --data-binary '@./xxxxx.png' -#
# curl也可以访问ftp服务器,如下:
# 下载ftp文件
curl -u 用户名:密码 ftp://localhost/user/avatar/xxxxx.png -O
# 上传文件到ftp
curl -u 用户名:密码 ftp://localhost/user/avatar/ -T ./xxxxx.png
另外,有时使用curl会出现一些莫名其妙的麻烦,这里分享一下我遇到的几种,大家留个眼熟:
# 一些网站https证书过期了,curl报curl: (60) SSL certificate problem: unable to get local issuer certificate
# 可以用-k忽略https证书校验
curl -k https://www.ywnds.com/
# 一些网站网速极慢,导致curl会阻塞很久,需要指定下网络超时时间
curl --connect-timeout 2 --max-time 2 http://www.baidu.com
# 一些网站会使用gzip压缩响应内容,这会导致curl报Warning: Binary output can mess up your terminal. Use "--output -" to tell
# 使用--compressed即可,使用gzip -d也可以
curl -H 'Accept-Encoding:gzip' http://www.baidu.com/ --compressed
curl -H 'Accept-Encoding:gzip' http://www.baidu.com/ | gzip -d
# 一些网站任然在使用GBK编码,这会导致curl结果乱码,甚至直接报Failed writing body错误,这时需要使用iconv转换一下编码
curl -i http://www.xxxxx.com | iconv -f GBK
# 有时,你想在输出响应结果后再输出一个换行,避免多次curl的结果混在一行上
curl -w '\n' http://localhost/health
可以发现,curl几乎可以定义HTTP请求的方方面面,因此,我经常使用curl作为与同事间沟通接口细节的工具,我也看到过不少厂商的api文档,直接使用curl来定义,毕竟对于开发者来说,任何文档都没有代码解释得清楚。
mysql命令使用起来比较简单,如下:
mysql -h localhost -P 3306 -u root -psecret test_db
执行上面命令后,就会进入SQL交互界面,然后你就可以执行SQL语句了,注意指定密码的方式,-p与secret之间不能有空格。
其它比较有用的选项如下:
选项 | 作用 |
---|---|
-A | 不加载表名、列名等元数据,网络不好时可加快命令启动速度,代价是按Tab键无法自动补齐表名与列名了 |
--default-character-set utf8mb4 | 指定字符集,避免某些环境下的乱码 |
-e | 指定要执行的SQL,执行完后直接退出,不进入命令交互界面 |
-N | 查询结果中不展示列名 |
-t | 查询结果以表格形式输出 |
-B | 查询结果不以表格形式输出,字段以tab分隔 |
-X | 查询结果以XML形式输出 |
-U | 安全地执行查询与更新,比如你操作大表时忘记了加limit这种 |
--pager | 指定结果分页展示器,默认是标准输出,--pager=less表示使用less作为分页展示器 |
这里面比较有意思的是pager,pager可以设置成任何linux命令,来处理查询结果,如下:
$ mysql -h localhost -P 3306 -u root -psecret test_db
# 未设置pager的情况,查询结果直接输出
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
+-------+------------------------------------+---------------------+---------------------+-------+----------+
| id | log_info | create_time | update_time | seq | add_time |
+-------+------------------------------------+---------------------+---------------------+-------+----------+
| 7706 | DOwhV4Odbuf0wVwyZxrJFbBOEGF0fhf7Ab | 2021-04-18 17:22:48 | 2022-03-11 00:25:49 | 7706 | 0 |
| 8155 | abc | 2021-04-18 16:55:42 | 2022-07-18 21:28:29 | 8155 | 0 |
| 14535 | uAXFeCJ3jtxhiQJS26q43MarWL5M | 2021-04-18 17:06:54 | 2022-06-26 21:19:42 | 14535 | 0 |
| 15303 | abc | 2021-04-18 17:30:18 | 2022-05-04 00:38:15 | 15303 | 0 |
| 31933 | cLfKXMYkaIta16kzmyWKFC | 2021-04-18 17:06:19 | 2022-09-14 20:14:10 | 31933 | 0 |
| 32503 | Rb6yMJ6fvViF28YWN9GA | 2021-04-18 17:33:59 | 2022-03-14 17:38:04 | 32503 | 0 |
+-------+------------------------------------+---------------------+---------------------+-------+----------+
6 rows in set (0.29 sec)
# 设置pager为cat -n,为输出内容添加行号
mysql> pager cat -n
PAGER set to 'cat -n'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
1 +-------+------------------------------------+---------------------+---------------------+-------+----------+
2 | id | log_info | create_time | update_time | seq | add_time |
3 +-------+------------------------------------+---------------------+---------------------+-------+----------+
4 | 7706 | DOwhV4Odbuf0wVwyZxrJFbBOEGF0fhf7Ab | 2021-04-18 17:22:48 | 2022-03-11 00:25:49 | 7706 | 0 |
5 | 8155 | abc | 2021-04-18 16:55:42 | 2022-07-18 21:28:29 | 8155 | 0 |
6 | 14535 | uAXFeCJ3jtxhiQJS26q43MarWL5M | 2021-04-18 17:06:54 | 2022-06-26 21:19:42 | 14535 | 0 |
7 | 15303 | abc | 2021-04-18 17:30:18 | 2022-05-04 00:38:15 | 15303 | 0 |
8 | 31933 | cLfKXMYkaIta16kzmyWKFC | 2021-04-18 17:06:19 | 2022-09-14 20:14:10 | 31933 | 0 |
9 | 32503 | Rb6yMJ6fvViF28YWN9GA | 2021-04-18 17:33:59 | 2022-03-14 17:38:04 | 32503 | 0 |
10 +-------+------------------------------------+---------------------+---------------------+-------+----------+
6 rows in set (0.29 sec)
# 设置pager为grep abc,只查看包含abc的行
mysql> pager grep abc
PAGER set to 'grep abc'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
| 8155 | abc | 2021-04-18 16:55:42 | 2022-07-18 21:28:29 | 8155 | 0 |
| 15303 | abc | 2021-04-18 17:30:18 | 2022-05-04 00:38:15 | 15303 | 0 |
6 rows in set (0.29 sec)
# 设置pager为less分页器,这样可以在查询结果中翻页与搜索了
mysql> pager less -iSX
PAGER set to 'less -iSX'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
+-------+------------------------------------+---------------------+---------------------+-------+----------+
| id | log_info | create_time | update_time | seq | add_time |
+-------+------------------------------------+---------------------+---------------------+-------+----------+
| 7706 | DOwhV4Odbuf0wVwyZxrJFbBOEGF0fhf7Ab | 2021-04-18 17:22:48 | 2022-03-11 00:25:49 | 7706 | 0 |
| 8155 | abc | 2021-04-18 16:55:42 | 2022-07-18 21:28:29 | 8155 | 0 |
| 14535 | uAXFeCJ3jtxhiQJS26q43MarWL5M | 2021-04-18 17:06:54 | 2022-06-26 21:19:42 | 14535 | 0 |
| 15303 | abc | 2021-04-18 17:30:18 | 2022-05-04 00:38:15 | 15303 | 0 |
| 31933 | cLfKXMYkaIta16kzmyWKFC | 2021-04-18 17:06:19 | 2022-09-14 20:14:10 | 31933 | 0 |
| 32503 | Rb6yMJ6fvViF28YWN9GA | 2021-04-18 17:33:59 | 2022-03-14 17:38:04 | 32503 | 0 |
+-------+------------------------------------+---------------------+---------------------+-------+----------+
(END)
# 设置pager为md5sum -,这样可以为查询结果生成md5
# 对比md5就能知道当前时间段数据是否产生了变化
mysql> pager md5sum -
PAGER set to 'md5sum -'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
40ed2c49a72ff68ba11b97d843bb4da2 -
6 rows in set (0.29 sec)
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
40ed2c49a72ff68ba11b97d843bb4da2 -
6 rows in set (0.31 sec)
# 先使用cat将第一次查询结果保存到文件中,然后使用icdiff对比查询结果是否变化
mysql> pager cat > result.txt
PAGER set to 'cat > result.txt'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
6 rows in set (0.31 sec)
mysql> pager icdiff result.txt /dev/stdin
PAGER set to 'icdiff result.txt /dev/stdin'
mysql> select * from app_log where create_time >= '2021-04-18' and create_time < '2021-04-18 17:35:00';
如果要恢复pager的话,只需要输入nopager并回车即可。
另外,对于mysql的相关命令行选项,是可以永久配置在mysql.cnf配置文件中的,这样可以避免每次都要在命令行带上自己常用的选项,如下:
$ sudo vim /etc/mysql/conf.d/mysql.cnf
[mysql]
default-character-set=utf8mb4
pager=less -iSFX
safe-updates=TRUE
一般我们使用ssh来登录并管理远程主机,比如安装软件、排查问题等,常见用法如下:
# 登录到主机192.168.0.1上,使用work用户,然后输入work用户密码即可
$ ssh work@192.168.0.1
# -p指定端口,有时主机为了安全会不使用默认的22端口
$ ssh work@192.168.0.1 -p 8022
# 直接在远程主机上执行命令
$ ssh work@192.168.0.1 -e 'ping www.baidu.com'
PING www.a.shifen.com (112.80.248.76) 56(84) bytes of data.
64 bytes from 112.80.248.76 (112.80.248.76): icmp_seq=1 ttl=54 time=19.1 ms
64 bytes from 112.80.248.76 (112.80.248.76): icmp_seq=2 ttl=54 time=19.9 ms
除了远程登录到主机上,我们经常需要上传下载文件,如下:
# scp和ssh是一个软件包的,为上传下载场景定制的命令,用法如下:
# 下载192.168.0.1主机上/home/work/logs/app.log到本机的logs目录下
$ scp work@192.168.0.1:/home/work/logs/app.log ./logs/
# 上传本机./logs/app.log文件到192.168.0.1主机上/home/work/logs/目录里
$ scp ./logs/app.log work@192.168.0.1:/home/work/logs/
# 实际上scp的源文件与目标文件都可以是远程主机上的,如下:
$ scp work@192.168.0.1:/home/work/logs/app.log work@192.168.0.2:/home/work/logs/app.log
# 实际上使用ssh也能上传下载
# 使用ssh下载,其中pv命令是为了查看进度,换成cat其实也可以
$ ssh work@192.168.0.1 'cat /home/work/logs/app.log' | pv > ./logs/app.log
# 使用ssh上传,其中pv命令是为了查看进度,换成cat其实也可以
$ pv ./logs/app.log | ssh work@192.168.0.1 'cat > /home/work/logs/app.log'
# 对比远程文件与本地文件内容
$ ssh work@192.168.0.1 'cat /path/to/remotefile' | icdiff /path/to/localfile –
如果你觉得这样上传下载好麻烦,可以使用sshfs将远程主机上的目录,直接挂载到本机上,这样你就可以像访问本机文件一样访问远程文件了。
sshfs是基于fuse机制与ssh协议实现的用户态文件系统,只要是你能用ssh的地方都能用sshfs,并且由于是用户态的文件系统,因此它并不像NFS那样需要特殊权限,只要网络连通即可,使用方法如下:
# sshfs挂载目录
$ sshfs work@192.168.0.1:/home/work/logs /home/work/logs
# 列出文件
$ ls /home/work/logs
app_2022-01-23T12:05:51.log app_2022-01-23T12:05:53.log app_2022-01-23T12:05:57.log app_2022-01-23T12:06:00.log
# 使用ssh列出远程目录,与上面结果是一样的
$ ssh work@192.168.0.1 'ls /home/work/logs'
app_2022-01-23T12:05:51.log
app_2022-01-23T12:05:53.log
app_2022-01-23T12:05:57.log
app_2022-01-23T12:06:00.log
也许你会奇怪,为啥上面结果一个没换行而另一个换行了,这是因为ls会检测输出对象是否是终端,如果是终端就会横排展示,否则竖排展示,不信你可以ls | cat看看。
其实好多命令都会有这种行为,比如curl默认不显示进度,但如果输出对象不是终端,如curl|cat,就会显示出进度信息。
一旦你这样用着ssh,就会发现每次都需要输入密码好麻烦,好在ssh提供了私钥认证机制,因此我们可以生成一对公私钥来避免每次都要输入密码,如下:
# 使用ssh-keygen生成ssh密钥对,使用rsa算法,长度4096,生成的密钥默认~/.ssh目录下,私钥叫id_ras,公钥叫id_ras.pub
ssh-keygen -o -t rsa -b 4096 -C "zhangsan@example.com"
# 使用ssh-copy-id将公钥上传到目标主机上
ssh-copy-id -i ~/.ssh/id_rsa.pub work@192.168.0.1
# 没有ssh-copy-id时,这样上传公钥也是可以的
cat ~/.ssh/id_rsa.pub | ssh work@192.168.0.1 'cat >> ~/.ssh/authorized_keys'
# 测试公钥是否生效,如果看到Authentication succeeded (publickey).字样,说明成功
ssh -vT work@192.168.0.1 </dev/null
是的,相信不少用过github/gitee/gitlab的同学会感觉到熟悉,其实github等也是一样使用ssh密钥来避免git操作需要输入密码的,配置过程是类似的:
有时,两个主机A、C之间网络并不互通,但A和B互通,B和C互通,这种情况下,我们可以利用B主机做一个端口转发,使得主机A可以访问主机C上的网络服务。
能够实现端口转发的软件有很多,如iptables、socat、portmap、rinetd等,而ssh也是常见的一个,用法如下:
# ssh本地端口转发,在A机器上执行,会在A机开启2001端口,并将此端口数据发到B机,B机又转发到C机的80端口
ssh -fgN -L2001:host_c:80 work@host_b
# ssh远端端口转发,在B机器上执行,在A机开启22333端口(由A机sshd服务打开),将A机22333端口流量通过B机(中转机)转到C机的80端口上
ssh -fgN -R 22333:host_c:80 work@host_a
# ssh远端动态端口转发,在B机器上执行,会在A机开启2222端口开放socks代理服务,被代理的数据会发到B机,B机又转发到任何B机能访问的网络服务上
ssh -fgN -R 2222 work@host_a
# ssh本地动态端口转发,在A机器上执行,会在A机开启2222端口开放socks代理服务,被代理的数据会发到B机,B机又转发到任何B机能访问的网络服务上
ssh -Nfg -D 2222 work@host_b
# A机通过本机socks代理,登录到C机
ssh -o ProxyCommand='ncat --proxy 127.0.0.1:2222 --proxy-type socks5 %h %p' work@host_c
# A机通过B机ssh登录C机,其实就是两次ssh登录过程
ssh -t work@host_b 'ssh work@host_c'
为啥会有本地端口转发与远端端口转发两种转发方式呢?
这和允许网络连接的方向有关,比如有些情况下,B能连接到A,但A不能连接到B,这时就需要使用远端端口转发了!
除了这些常用的命令外,Linux中还有很多其它类型的工具命令,比如:
这里就不一一介绍了,后面有机会可以单独拿出来介绍介绍。