在上一篇中讲了如何刷 Openwrt,这一篇主要讲一些 Openwrt 的东西,以及配置相关的内容。我有一个主路由器,设置分配的局域网地址为 192.168.1.x,给内网中分配的地址也是 192.168.1.x 开头。
但是 Openwrt 默认为 AP 模式,我想要从主路由器 LAN 口连出到新的这个 Openwrt 路由器上,那么便得设置 Openwrt 路由器为 Router 模式以便于级联。
在设置路由器模式之前先来看看这几个接口,否则怎么都不会明白怎么配置的。
Openwrt 的接口名字太多,最早接触路由器的时候只知道 WLAN 口,LAN 口,后来接触 Linux 才慢慢知道 eth0, lo 等等接口,但是在 Openwrt 上接口中突然冒出来一堆看着名字熟悉,却不知道什么作用的接口。今天配置 LAN ,WAN 口时还差点把 MR12U 搞砖,幸亏昨天刷了不死 boot。
可以使用 ifconfig
来查看设备,常见的几个端口:
lo
虚拟设备端口,自身回环设备,一般指向 127.0.0.1ra0
rai0
成对出现,无线设备,对应各自的 SSID,分别是 2.4G 和 5Gpppoe-wan
虚拟设备,常见的拨号宽带上网eth0
物理网卡, eth0.1 或者 eth0.2 都是从此设备虚拟而出。br-lan
虚拟设备,用于 LAN 口设备桥接,用来使多个虚拟或物理网络接口的行为好像他们仅有一个网络接口一样。目前路由器普遍将有线 LAN 口(一般四个)和 WIFI 无线接口桥接在一起作为统一的 LAN。可以使用 brctl show
来查看使用情况。eth1
如果路由器有两块网卡,一般 eth1 作为 WAN 口wlan0
一般是无线网卡,无线端口wlan1
另一块无线网卡可以使用如下命令来查看 br-lan
配置
brctl show
bridge name bridge id STP enabled interfaces
br-lan 7fff.64098005e1bb no eth0.1 rai0 ra0
br-lan
= eth0.1 + rai0 + ra0,即将有线 LAN 口和无线网统一划分为 LAN。
下面张图比较直观:
LAN 是设置局域网内的相关属性,可以设置内网的 IP,桥接的端口。比如我们默认使用 192.168.1.1 访问,可以修改为 192.168.9.1,生效后内网分配的 ip 网段就会变成 192.168.9.x 。LAN 口的协议为【静态地址】。下一次访问路由器管理页面就需要使用 192.168.9.1 了。
System->Software->在 Filter 栏里面输入 -zh-cn
点击搜索
找到 luci-i18n-base-zh-cn 点击前面的安装。然后去设置语言即可。
路由器模式也就是最常见的无线模式,通过有线连接路由器 WAN 口至互联网,并发射无线提供局域网络。由于 OpenWrt 默认只有 LAN 接口,我们需要添加 WAN 接口。
Openwrt morning 配置只有上述图片的 LAN 口,下面的 WAN 口通过如下方法添加。
点击下方的“添加新接口”
为了便于区分,接口名称建议使用 WAN。按照网络接入类型,选择 DHCP(从外网自动获取 ip 地址),静态 ip 或者 PPPoE 拨号即可。其它设置如图,请勿选择“在多个接口上创建桥接”,最后点击提交。
提交后选择刚刚创建的 WAN 接口,点击“防火墙设置”,选择 WAN 并保存即可。
这时需要再次回到 LAN 接口,点击编辑。
选择“物理设置”,确保“桥接接口”为选中,接口中不选中“以太网适配器”。确认后保存并应用,至此所有配置完成,连接网线即可使用。
一种需求是将有线网络转为无线 WiFi,使得 WiFi 网络和其他原来在有线网络中的设备在同一子网中,比如说一级路由网段 192.168.2.X,然后添加无线路由器分享 WiFi,使得 WiFi 上的设备网段依然是 192.168.2.X,不产生新子网,没有 NAT,一级路由下面设备可以访问二级路由下任何设备不需要做端口转发。
有线中继的方式下,二级路由不会 DHCP,并且网段相同。如果要实现这种模式,OpenWrt 需要将无线网卡接口和 WAN 划分到一起,路由器 Master 模式 + 无线 AP.
此时连接 WiFi,会发现客户端通过一级路由分配 IP 地址,网关是上一级路由的网关。
有些时候并没有有线网络可以连接到 OpenWrt 路由器 WAN 口,但是有 WiFi 可以连接,那么可以将路由器转变成一个 WiFi 信号放大器,扩展 WiFi 信号覆盖范围。
无线中继模式使用无线网络接入互联网,并生成一个新的 SSID。无线桥接模式无需更改有线连接接口设置。打开无线接口设置,点击搜索。在自动弹出的设置页面中,填写上级无线密码。新网络的名称使用默认 WWAN 即可。防火墙区域选择 WAN,在这里请勿选择“重置无线配置”。在保存并应用后就完成了所有设置。
假如有一台主路由已经接入了互联网(内网网段是 192.168.2.1/24),现在要在一台从路由 (OpenWrt) 上无线桥接到主路由。
luci-proto-relay
和 relayd
无线 AP(接入点) 模式多应用于公共场所,所有无线设备将被桥接至以太网接口,由上级网关负责 DHCP。在设置完成后 AP 所在路由器将无法访问。
首先打开 LAN 接口或者 WAN 接口,选择“物理设置”,确保“桥接接口”为选中。在下方接口选中“以太网适配器”以及“无线网络”,保存并应用即可。
至于无线加密设置以及 DHCP 设置较为简单,自行在“网络”分类下查找即可。
总结性的归纳一下,中继和桥接都可以扩大原来的信号范围。
而无线桥接模式不参与
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free 中继模式 (Repeater) 下,路由器会通过无线 方式与一台可以上网的无线路由器建立连接,用来放大可以上网的无线路由器上的无线信号(放大后的无线信号和原来的无线路由器名称一致)。适用于扩大一台可以上网的无线路由器的信号覆盖范围。中继一边接受信号,一边发射自己的无线信号。这种模式下无线路由器以 Client 方式接入主路由,另外新增虚拟接口提供无线接入。
而桥接模式 (Bridge) 下,路由器通过无线方式与可以上网的无线路由器连接,而放大后的无线信号名称可以和原来的不一样。桥接模式下路由器可以设定自己的 DHCP,提供一个自己的局域网。
中继和桥接模式都可以通过无线方式扩大信号,区别在于扩大后的无线信号名字不一样。
今天翻箱倒柜竟然找出了我的 TP-LINK MR12U,很早之前因为 3G 上网卡而买的便携式路由,突然脑袋一热,干嘛不试试刷个 Openwrt 呢。记得当时是没有支持的,但是一搜竟然发现了 Openwrt 有官方支持了。于是开始动手。
这里主要记录一下 MR12U v1.0 版本的过程,但是感觉其他路由器异曲同工,掌握了一种方法其他路由器也是类似的原理。刷机的过程有风险,因此一定要做好充分的调查和心理准备。很早之前写过一篇文章讲防止 Android 刷机变砖 ,我利用其中用到的方法一直刷机至今。说到底,终究要知道自己做的每一步是什么含义,出现的每一个术语是什么含义。
我一直坚信着“授人以鱼不如授人以渔”的理念,因此我在文中会把我参考的所有文章以及想法过程都记录下来,以便于在以后刷其他路由器的时候能够更加快速,并且如果有其他人能看到也能更加明白。
首先什么是 Openwrt,Openwrt 是一个适合嵌入式设备的 Linux 发行版 1,相对原厂固件而言,OpenWrt 不是一个单一静态的固件,而是提供了一个可添加软件包的可写的文件系统。这使用户可以自由的选择应用程序和配置,而不必受设备提供商的限制,并且可以使用一些适合某方面应用的软件包来定制你的设备。对于开发者来说,OpenWrt 是一个框架,开发者不必麻烦的构建整个固件就能得到想要的应用程序;对于用户来说,这意味着完全定制的能力,与以往不同的方式使用设备,OPKG 包含超过 3500 个软件。
默认使用 LuCI 作为 web 交互界面。
因为其强大的扩展性,所以能用 Linux 做到的事情,Openwrt 几乎都能做到,而如今生活在墙内,路由器很重要的一个功能便是翻墙,结合 Shadowsocks,pdnsd 等等 Openwrt 可以做到透明代理。去除这个硬性需求外,其他比如:
甚至定时关 WIFI,开 WIFI,都几乎是一行命令。
其实 Openwrt 自身维护一个兼容路由器列表 https://wiki.openwrt.org/toh/start 。在购买或者刷机之前都可以看一眼。网上推荐的很多支持比较好,性价比比较高的路由器,NETGEAR 的比较多,WNDR 4300,WNDR 3700 和 WNDR 3800 都是比较流行的路由器。在选择一款路由器上,其实最好的不是性能最强的,而是最适合自己的。知乎上有个回答说得很好:
对于 Openwrt 用户而言,因地制宜合理发挥才是最优选择。对于家用环境而言更适合性能向(千兆局域网、强劲的性能、MIMO&5G hz 表现优异),对于差旅党、安全狗而言便携路由器更具备实用性。所以在初入 openwrt 圈子的前提下建议先上手一款大方向上适合自己的机器。
然后下面是一些链接,在刷机或者购机之前都看一眼比较好:
在网上搜索了一圈,很少有 MR12U v1.0 版本的教程,倒是找到一个 v2 版本的详细教程。但是 v1 版本的刷机和 v2 相差不大。v2 版原帖 http://www.right.com.cn/forum/thread-169358-1-1.html 。
硬件:TP-MR12U(v1) 路由器一个,网线一根,PC 一台,戳菊花工具一根。
软件:如果在 Windows 下需要 TPRouter 用于修改固件版本信息。putty: 以命令行方式登陆路由器。WinSCP: 上传文件到路由器。而如果在 Linux 下,打开终端即可。
固件:
(1) 对应的 Openwrt 解锁 U-Boot 分区固件,文件名为 openwr-ar71xx-generic-tl-mr11u-v2-squashfs-factory.bin 。看清楚是 11U 的不是 12U 的,因为 12U(v1) 和 12U(v2) 硬件不同,12U(v1) 需要使用 11U(v2) 的固件。这个也是我们第一次需要刷入的固件。
(2)openwrt 适用于 MR12Uv1 的官方固件,文件名为 openwrt-15.05.1-ar71xx-generic-tl-mr12u-v1-squashfs-factory.bin。如果你不在意用的 openwrt 不是最新版的话可以不用。
(3) 不死 boot 固件,文件名为 breed-ar9331-mr12u.bin。
刷机过程:
具体过程总结,使用 ssh root@192.168.1.1
连上路由器
root@mr12u:/tmp# ifconfig eth0
eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:16514 errors:0 dropped:0 overruns:0 frame:0
TX packets:13371 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2388356 (2.2 MiB) TX bytes:2518125 (2.4 MiB)
Interrupt:4
记住上述 HWaddr 后面 MAC 地址,后面会用到。
breed-ar9331-mr12u.bin
使用 scp breed-ar9331-mr12u.bin root@192.168.1.1:/tmp/
再使用 ssh 连山路由器,使用如下命令
cd /tmp
mtd write breed-ar9331-mr12u.bin u-boot
# 成功后 reboot 重启路由器即可。
如果这一步出现如下信息,是使用了 Openwrt 官方的固件,默认是锁了 u-boot
的。
Could not open mtd device: u-boot
Can't open device for writing!
当前的 Openwrt 锁了 u-boot
分区,需要刷入一个未锁分区的 Openwrt 固件,可以上论坛找一下,将解锁分区的固件上传到 /tmp 目录,使用 mtd 命令写入固件 firmware 分区。
cd /tmp mtd write openwr-ar71xx-generic-tl-mr11u-v2-squashfs-factory.bin firmware
u-boot
分区后可按照如下步骤进入 u-boot
控制台。u-boot
界面。u-boot
会将 MAC 地址重置,需要将 MAC 地址还原回来,不然有些功能无法使用,比如无线功能,将之前备份好的 MAC 地址放入 TP-LINK 设置下的 MAC 地址位置。u-boot
中固件更新刷入新固件即可。昨天刷 Openwrt,需要在本机和路由器之间传输文件,本来在 Windows 上知道有一个 WinSCP 可以来 GUI 上传输,其实 Linux 下更加简单,使用 scp 命令一行就能解决,scp 的语法也非常简单。
scp
是 secure copy 的简写,用于在 Linux 下进行远程拷贝文件的命令,和它类似的命令有 cp,不过 cp 只是在本机进行拷贝不能跨服务器,而且 scp 传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system 时,用 scp 可以帮你把文件移出来。另外,scp 还不占资源,不会提高多少系统负荷,在这一点上,rsync 就远远不及它。虽然 rsync 比 scp 会快一点,但当小文件众多的情况下,rsync 会导致硬盘 I/O 非常高,而 scp 基本不影响系统正常使用。
scp 参数 原路径 目标路径
scp 是 Linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令。Linux 的 scp 命令可以在 Linux 服务器之间复制文件和目录。
其他的命令参数
-1 强制 scp 命令使用协议 ssh1
-2 强制 scp 命令使用协议 ssh2
-4 强制 scp 命令只使用 IPv4 寻址
-6 强制 scp 命令只使用 IPv6 寻址
-B 使用批处理模式(传输过程中不询问传输口令或短语)
-C 允许压缩。(将 -C 标志传递给 ssh,从而打开压缩功能)
-p 保留原文件的修改时间,访问时间和访问权限。
-q 不显示传输进度条。
-r 递归复制整个目录。
-v 详细方式显示输出。scp 和 ssh(1) 会显示出整个过程的调试信息。这些信息用于调试连接,验证和配置问题。
-c cipher 以 cipher 将数据传输进行加密,这个选项将直接传递给 ssh。
-F ssh_config 指定一个替代的 ssh 配置文件,此参数直接传递给 ssh。
-i identity_file 从指定文件中读取传输时使用的密钥文件,此参数直接传递给 ssh。
-l limit 限定用户所能使用的带宽,以 Kbit/s 为单位。
-o ssh_option 如果习惯于使用 ssh_config(5) 中的参数传递方式,
-P port 注意是大写的 P, port 是指定数据传输用到的端口号
-S program 指定加密传输时所使用的程序。此程序必须能够理解 ssh(1) 的选项。
其中 -r
和 -P
是经常用到的两个选项,一个用来配置传输整个目录,一个用来指定端口
scp 命令的实际应用
命令格式:
scp local_file remote_username@remote_ip:remote_folder # 文件拷贝到远程目录中
或者
scp local_file remote_username@remote_ip:remote_file # 拷贝到远程文件,并重命名
或者
scp local_file remote_ip:remote_folder
或者
scp local_file remote_ip:remote_file
主要使用 -r
参数,linux 下很多命令都是 -r
来指定文件夹,或者记住 recursive
。
命令格式:
scp -r local_folder remote_username@remote_ip:remote_folder
或者
scp -r local_folder remote_ip:remote_folder
从远程复制到本地的 scp 命令与上面的命令类似,只要将从本地复制到远程的命令后面 2 个参数互换顺序就行了。
命令:
scp root@<remote_ip>:/path/to/file.tar.gz /local/path/
同理,从远程服务器拉取整个文件夹
scp -r root@<remote_ip>:/path/folder/ /local/folder
如果远程服务器使用非标准端口,那么可以用 -P port
选项指定端口。
scp -P some_port root@ip:/path/to/file /local/
最近了解了一下 Cron,也在 WizNote 上 记录了一些笔记。学习一个新命令最好的方法就是将其用于实践。于是正好在 Openwrt 路由器上跑一下。
使用 crontab -e
编辑 Openwrt 的定时任务,添加如下
# Reboot at 4:30am every day
# Note: To avoid infinite reboot loop, wait 70 seconds
# and touch a file in /etc so clock will be set
# properly to 4:31 on reboot before cron starts.
30 4 * * * sleep 70 && touch /etc/banner && reboot
这个 task 将在每天 4:30am 的时候重启路由器。
需要注意的是,一定要延迟重启,否则可能无限重启,官方给出的配置 1 中,在 sleep 70
秒之后,使用 touch
写文件,因为路由器如果没有及时联网从 NTP 服务器上获取到时间,那么路由器的系统时间和重启的系统时间便一样,如果修改过文件,Openwrt 开机后会把最后修改或者访问的文件时间作为默认系统时间。因此延迟 70s 重启,可以避免这个问题。
小知识点,OpenWrt 会在开机时检测 /etc 目录下最近修改的文件,并将该文件修改的时间戳作为开机的初始时间。
一个 crontab 的配置文件,通过前五个域来表示时刻,时期,甚至是时间段。每一个域中,可以包含 *
或者逗号分割的数字,或者 -
连接的数字。
* * * * * command to be executed
- - - - -
| | | | |
| | | | +----- day of week (0 - 6) (Sunday=0)
| | | +------- month (1 - 12)
| | +--------- day of month (1 - 31)
| +----------- hour (0 - 23)
+------------- min (0 - 59)
*
号表示任意而空格分割的六个域分别表示:
注意事项:
/2
表示没两分钟执行一次 或者 /10
表示每 10 分钟执行一次,这样的语法格式并不是被所有系统支持。更多具体关于 crontab 的内容,可以参考 WizNote。
根据官方的介绍1,HTTrack 是一个易用的离线浏览工具,他允许用户从万维网中离线备份某一个网站,包括建立层叠的目录,HTML,图片,以及其他文件。工具在 GPL 协议下开源。
最近主要是因为想要备份 http://www.runningman2015.com/guidang/ 这个网站,突然想到了这个工具。在此之前曾经想要自己用 scrapy 提取网站结构,然后存到数据库,想了一下,直接一个命令可以实现的事情,完全可以不用 scrapy 了。
在許多Unix-like系統下,只需要用包管理工具安裝httrack即可。例如Debian使用
sudo apt-get install httrack webhttrack
该工具集包含一个命令行 httrack
和 一个WEB界面的 webhttrack
。如果想要直观的运行 HTTrack,可以直接使用 webhttrack
。 HTTrack 官方提供 Windows 版本,可以直接去官网1下载。
一個使用例子:
httrack "http://www.runningman2015.com/" -O "/home/einverne/rm/" "+*.runningman2015.com/*" -v
它的意思是:以http://www.runningman2015.com/ 为起始URL,输出到 /home/einverne/rm/ 文件夹,范围是 runningman2015.com 域名下的所有文件,并显示所有错误信息(verbose)。
其他参数
O 镜像后本地路径 -O path/to/local
w 镜像网站 (–mirror) W mirror web sites, semi-automatic (asks questions) (–mirror-wizard) 更加自动化的备份
更多参考官网手册2。
当然新手也可以直接运行 httrack
命令,该命令会自动产生一个向导,选择123 即可。
名称 | 网址 | 平台 | 优缺点 |
---|---|---|---|
Teleport Pro | http://www.tenmax.com/teleport/pro/index.htm | Windows | 整站备份,网站结构清晰,只支持单一平台,收费 |
Cyotek WebCopy | https://www.cyotek.com/cyotek-webcopy | Windows with .NET 4.6 | 整站备份,免费 |
wget -r --no-parent -e robots=off http://www.example.com
wget -m -p -E -k www.example.com
镜像站点功能很强大,但是下载离线的数据非结构化数据,镜像功能对与纯静态HTML站点非常有效,但是对于目前互联网上的大部分 JS 动态网站却无能为力,镜像后容易都是内容。
Mint 下有一个 Driver Manager 驱动管理,手贱升级了一下到 378,下载,自动安装,重启倒是没有什么但问题,最关键的是,第二次重启的时候直接黑屏,之前也遇到过一回,记忆中是修改了 /etc/X11/xorg.conf
才修复这个问题,没想到这一次又遇到了这个问题。但是无论我怎么修复笔记本始终黑屏在,开机闪过 Linux Mint 的 logo 之后始终无法启动 x server。
凭借这记忆中的印象,在启动登录之后 Ctrl+Alt+1~7 1~7中的任意一个键,进入 tty 终端,用 root 用户登录,成功后在其中 apt purge nvidia*
移除所有 NVIDIA 驱动。然后重启。
在第一步尝试不成功之后,尝试修改 xorg.conf
文件,在 /etc/X11
目录下发现了,原始的 xorg.conf 的备份,在尝试恢复以前版本的时候,X server 不断的报错,在查看 cat /var/log/Xorg.0.log
的时候发现 xorg.conf 配置有很大问题,尝试恢复原始设置失败,尝试自己手动修改配置失败。
Section "ServerLayout"
Identifier "layout"
Screen 0 "nvidia"
Inactive "intel"
EndSection
Section "Device"
Identifier "intel"
Driver "intel"
BusID "PCI:0@0:2:0"
Option "AccelMethod" "SNA"
EndSection
Section "Screen"
Identifier "intel"
Device "intel"
EndSection
Section "Device"
Identifier "nvidia"
Driver "nvidia"
BusID "PCI:1@0:0:0"
Option "ConstrainCursor" "off"
EndSection
Section "Screen"
Identifier "nvidia"
Device "nvidia"
Option "AllowEmptyInitialConfiguration" "on"
Option "IgnoreDisplayDevices" "CRT"
EndSection
甚至在终端下安装了 370, 331 版本的驱动多次尝试,尝试使用 NVIDIA 自带的命令 nvidia-xconfig
重新生成 xorg.conf ,终究无法成功启动 Cinnamon.
dpkg-reconfigure nvidia-331
寻找解决办法的时候,偶然间在论坛上看到有人留言,说,删除 xorg.conf 文件之后一切就正常了,我保证尝试的心态,将 mv xorg.conf xorg.conf_backup
之后,启动 startx
竟然进去了 Cinnamon。顿时感觉有希望,直接重启,用自己账户登录,果然可以了,同时还发现竟然把鼠标延迟的问题解决了。有时间真应该好好研究下 Linux 的启动过程和驱动配置。
关于 Xorg,X.org 项目旨在创建和维护开源可在发行版 X11,在运行的硬件和图形界面之间提供接口。
关于 X server,是 X.org 项目的部分,是 X Window System 的重要组成部分。xorg.conf 文件是 X server 的主要配置文件
Mint 中系统额外的默认 Xorg 配置地址 /usr/share/X11/xorg.conf.d
通过 crontab 命令可以让我们在固定的时间点或者特定时间间隔执行指定的系统指令或 shell 脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。
cron 的名字,没有特别明确的定义,cron 名字的由来是 Ken Thompson(Unix cron 的作者)说过的, cron 的名字由希腊语 time 的前缀 chron 得来 1。
cron 守护进程是一个由实用程序和配置文件组成的小型子系统,在几乎所有类 UNIX 系统上都可以找到某种风格的 cron。cron 的组件包括
一般来说,cron 配置文件或 cron 任务列表被称为 crontab(cron table)。
守护进程 cron 连续运行,每分钟检查一次配置文件中的修改。cron 读取系统范围的和针对用户的 crontab 、相应地更新事件调度计划并执行这一分钟内应该执行的所有命令。这个守护进程还会获取每个作业的输出(如果有输出的话),并把结果通过电子邮件发送给作业的所有者。
可以在三个位置定义与系统相关的作业:/etc/crontab
、/etc/cron.d
中的任何文件以及特殊目录 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly 和 /etc/cron.monthly:
crontab 命令主要给用户来维护 crontab 文件,执行定时任务,免去直接修改配置文件的操作。 crontab 可以用来安装 cron ,卸载和列出 cron 的任务。每一个用户都有其自己的定时任务列表,用户的任务保存在 /var/spool/cron/crontabs
文件夹下,以用户名为文件名,千万不要手动修改该目录下文件。推荐使用 crontab -e
来修改。
crontab [-u user] file
crontab [-u user] [ -e | -l | -r ]
如果 /etc/cron.allow
文件存在,只有存在该文件中的用户才能使用 cron。 如果 /etc/cron.deny
存在,那么只有不在该文件中的用户才能使用 cron。
如果两个文件都不存在,则看 site-dependent configuration parameters
配置,要不然 superuser 可以使用,要不然所有用户都可以使用。如果两个文件都存在,则优先使用 /etc/cron.allow
文件。不管 这两个文件存不存在 ,superuser 都可以使用。
常用的参数如下:
-u user:用来设定某个用户的 crontab 服务;
file:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。
-e:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。
-l:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。
-r:从 /var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。
-i:在删除用户的 crontab 文件时给确认提示。
更多其他参数,记得 man crontab
来查看。
crontab 的语法格式:
* * * * * command to be executed
- - - - -
| | | | |
| | | | +----- day of week (0 - 6) (Sunday=0)
| | | +------- month (1 - 12)
| | +--------- day of month (1 - 31)
| +----------- hour (0 - 23)
+------------- min (0 - 59)
一个标准的 crontab 配置需要符合如下:
分 时 日 月 星期 要运行的命令
一个 crontab 的配置文件,通过前五个域来表示时刻,时期,甚至是时间段。每一个域中,可以包含 *
或者逗号分割的数字,或者 -
连接的数字。
*
号表示任意,
逗号分割表示时刻, separator-
短横线连接,表示时间段, range of values/
表示间隔, 如果第一个域为 /2 ,则表示每隔两分钟, step value而空格分割的六个域分别表示:
注意事项:
/2
表示没两分钟执行一次 或者 /10
表示每 10 分钟执行一次,这样的语法格式并不是被所有系统支持。向 cron 进程提交一个 crontab 文件之前,首先要设置环境变量 EDITOR。cron 进程根据它来确定使用哪个编辑器编辑 crontab 文件。99% 的 UNIX 和 Linux 用户都使用 vi,如果你也是这样,那么你就编辑 $HOME 目录下的。profile 文件,在其中加入这样一行:
export EDITOR=vim
然后保存并退出。如果不设定,在第一次运行 crontab -e
时, crontab 会跳出选项选择合适的 Editor。
使用命令 crontab -e
。在该文件中加入如下的内容。
# (put your own initials here)echo the date to the console every
# 15minutes between 6pm and 6am
0,15,30,45 18-06 * * * /bin/echo 'date' > /dev/console
保存并退出。注意前面 5 个域用空格分隔。
在上面的例子中,系统将每隔 1 5 分钟向控制台输出一次当前时间。如果系统崩溃或挂起,从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些系统中,用 tty1 来表示控制台,可以根据实际情况对上面的例子进行相应的修改。在使用命令修改 crontab
文件之后会自动保存。
现在该文件已经提交给 cron 进程,它将每隔 15 分钟运行一次。同时,新创建文件的一个副本已经被放在 /var/spool/cron/crontabs
目录中,文件名就是用户名(即 einverne)。
使用 -l
参数列出 crontab 文件:
crontab -l
0,15,30,45 18-06 * * * /bin/echo `date` > dev/tty1
可以使用这种方法在 $HOME 目录中对 crontab 文件做一备份:
crontab -l > $HOME/mycron
这样,一旦不小心误删了 crontab 文件,可以用上面所讲述的方法迅速恢复。
编辑 crontab 文件
如果希望添加、删除或编辑 crontab 文件中的条目,而 EDITOR 环境变量又设置为 vi,那么就可以用 vi 来编辑 crontab 文件:
crontab -e
可以像使用 vi 编辑其他任何文件那样修改 crontab 文件并退出。如果修改了某些条目或添加了新的条目,那么在保存该文件时, cron 会对其进行必要的完整性检查。如果其中的某个域出现了超出允许范围的值,它会提示你。 我们在编辑 crontab 文件时,没准会加入新的条目。例如,加入下面的一条:
# DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month
30 3 1,7,14,21,26 * * /bin/find -name 'core' -exec rm {} \;
保存并退出。
crontab -r
千万别乱运行crontab -r
。它从 Crontab 目录(/var/spool/cron)中删除用户的 Crontab 文件。删除了该用户的所有 crontab 都没了。
给某一个用户新建 crontab 任务 2
sudo crontab -u einverne -e # 给 einverne 的用户设定定时任务,需要 superuser 权限
使用 cron 执行 Python 脚本,在 crontab -e 之后使用如下配置:
*/10 * * * * /usr/bin/python script.py
crontab file 启用 定时任务
使用实例
实例 1:每 1 分钟执行一次 Command
* * * * * Command
实例 2:每小时的第 3 和第 15 分钟执行
3,15 * * * * myCommand
实例 3:在上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 * * * myCommand
实例 4:每隔两天的上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 */2 * * myCommand
实例 5:每周一上午 8 点到 11 点的第 3 和第 15 分钟执行
3,15 8-11 * * 1 myCommand
实例 6:每晚的 21:30 重启 smb
30 21 * * * /etc/init.d/smb restart
实例 7:每月 1、10、22 日的 4 : 45 重启 smb
45 4 1,10,22 * * /etc/init.d/smb restart
实例 8:每周六、周日的 1 : 10 重启 smb
10 1 * * 6,0 /etc/init.d/smb restart
实例 9:每天 18 : 00 至 23 : 00 之间每隔 30 分钟重启 smb
0,30 18-23 * * * /etc/init.d/smb restart
实例 10:每星期六的晚上 11 : 00 pm 重启 smb
0 23 * * 6 /etc/init.d/smb restart
实例 11:每一小时重启 smb
* */1 * * * /etc/init.d/smb restart
实例 12:晚上 11 点到早上 7 点之间,每隔一小时重启 smb
0 23-7 * * * /etc/init.d/smb restart
cron 在 /var/log/syslog
中有相关日志,可以使用 tailf /var/log/syslog | grep cron
来查看 cron 运行日志。或者使用 sudo /etc/init.d/cron status
来查看 cron 运行状态。使用 restart
或者 start
来重启或者 启动 cron。
更新系统时间时区后需要重启 cron, 在 Ubuntu/Mint 中服务名为 cron。
如果遇到 cron 服务的时间和系统时间不在正确的时区中,可以使用
dpkg-reconfigure tzdata
来设置正确的时间,之后重启 cron 服务。如果设置确认之后 cron 的时区依然不对,那么重启一下机器尝试一下。
cron 使用 /usr/bin/sh
的命令,默认有以下内置变量:
HOME=user home directory
LOGNAME=user's login id
PATH=/usr/bin:/user/sbin:.
SHELL=/usr/bin/sh
有时我们创建了一个 crontab,但是这个任务却无法自动执行,而手动执行这个任务却没有问题,这种情况一般是由于在 crontab 文件中没有配置环境变量引起的。
在 crontab 文件中定义多个调度任务时,需要特别注环境变量的设置,因为我们手动执行某个任务时,是在当前 shell 环境下进行的,程序当然能找到环境变量,而系统自动执行任务调度时,是不会加载任何环境变量的,因此,就需要在 crontab 文件中指定任务运行所需的所有环境变量,这样,系统执行任务调度时就没有问题了。
不要假定 cron 知道所需要的特殊环境,它其实并不知道。所以你要保证在 shelll 脚本中提供所有必要的路径和环境变量,除了一些自动设置的全局变量。所以注意如下 3 点:
脚本中涉及文件路径时写全局路径;
脚本执行要用到 java 或其他环境变量时,通过 source 命令引入环境变量,如:
cat start_cbp.sh
!/bin/sh
source /etc/profile
export RUN_CONF=/home/d139/conf/platform/cbp/cbp_jboss.conf
/usr/local/jboss-4.0.5/bin/run.sh -c mev &
当手动执行脚本 OK,但是 crontab 死活不执行时,很可能是环境变量不正确,可尝试在 crontab 中直接引入环境变量解决问题。如:
0 * * * * . /etc/profile;/bin/sh /var/www/java/audit_no_count/bin/restart_audit.sh
每条任务调度执行完毕,系统都会将任务输出信息通过电子邮件的形式发送给当前系统用户,这样日积月累,日志信息会非常大,可能会影响系统的正常运行,因此,将每条任务进行重定向处理非常重要。 例如,可以在 crontab 文件中设置如下形式,忽略日志输出:
0 */3 * * * /usr/local/apache2/apachectl restart >/dev/null 2>&1
“/dev/null 2>&1” 表示先将标准输出重定向到 /dev/null,然后将标准错误重定向到标准输出,由于标准输出已经重定向到了 /dev/null,因此标准错误也会重定向到 /dev/null,这样日志输出问题就解决了。
将这条语句拆解开来看:
>
是重定向符/dev/null
是一个黑洞,任何发送给它的数据都会被丢弃2
是标准错误的文件描述符>
同上&
symbol for file descriptor1
标准输出描述符可以参考 Linux IO Redirection 来了解更多。
系统级任务调度主要完成系统的一些维护操作,用户级任务调度主要完成用户自定义的一些任务,可以将用户级任务调度放到系统级任务调度来完成,但不建议这么做,但是反过来却不行,root 用户的任务调度操作可以通过”crontab –u root –e”来设置,也可以将调度任务直接写入 /etc/crontab 文件,需要注意的是,如果要定义一个定时重启系统的任务,就必须将任务放到 /etc/crontab 文件,即使在 root 用户下创建一个定时重启系统的任务也是无效的。
在 crontab 中 % 是有特殊含义的,表示换行的意思。如果要用的话必须进行转义 \%
,如经常用的 date '+%Y%m%d'
在 crontab 里是不会执行的,应该换成 date '+\%Y\%m\%d'
。
更新系统时间时区后需要重启 cron, 在 ubuntu 中服务名为 cron:
service cron restart
Ubuntu/Mint 下启动、停止与重启 cron:
sudo /etc/init.d/cron start
sudo /etc/init.d/cron stop
sudo /etc/init.d/cron restart
如果想要系统在重启之后执行某个脚本,通过 crontab 可以实现:
编辑 crontab:
crontab -e
然后加入:
@reboot /usr/local/xxxxx
@reboot /usr/local/xxxxx.sh
上一篇讲了 shell 脚本的基本语法,然后这篇补一补标准输入输出重定向命令,以及管道命令。
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
管道命令的操作符是”|”, 它将前一个命令的输出给下一个命令作为输入。
command1 | command2 | command3
将前一个命令的输出作为下一个命令的输入,比如 cat text.txt | less
。 管道命令不处理错误输出。
> filename.txt
将 stdout 重定向到文件,如果不存在则创建,否则覆盖
: > filename
此操作将文件 filename 变为空文件, size 为0。文件不存在则创建,与 touch 命令相同。 冒号是一个占位,不产生任何输出
>>
追加到文件末尾
2> filename
重定向 stderr 的输出到文件
2>> filename
重定向并追加 stderr 到文件
&> filename
将 stdout 和 stderr 都重定向到 file
下面是一个例子:
cat *.txt | sort | uniq > result-file
# 对所有.txt文件的输出进行排序, 并且删除重复行.
# 最后将结果保存到"result-file"中.
在 crontab 任务中,需要使用 2>&1
来输出到日志
* * * * * command >> file.log 2>&1
command < file
将右边文件作为输入给左边的命令
command <<< string
后面接 string,将 string 内容给 command
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
$ command > /dev/null 2>&1
在类 Unix 系统中,我们通常会配置 Shell 的 ~/.bashrc
或者 /etc/profile
来设置用户的工作环境。一般系统可能会有
/etc/profile
/etc/bashrc
/etc/bash.bashrc
~/.bashrc
~/.profile
在理解这些文件之前,需要了解 Shell 不同的登录方式,login 登录和 interactive 模式。
关于 Shell 的历史和分类可以参考之前的文章
“login shell” 代表用户登入,比如使用 “su -“ 命令,或者用 ssh 连接到某一个服务器上,都会使用该用户默认 shell 启动 login shell 模式。
该模式下的 shell 会去自动执行 /etc/profile
和 ~/.profile
文件,但不会执行任何的 bashrc 文件,所以一般再 /etc/profile 或者 ~/.profile 里我们会手动去 source bashrc 文件。
启动场景:
sudo su -
此时配置加载顺序
/etc/profile -> /etc/bash.bashrc
~/.profile -> ~/.bashrc
而 no-login shell 的情况是我们在终端下直接输入 bash 或者 bash -c “CMD” 来启动的 shell.
该模式下是不会自动去运行任何的 profile 文件。
/etc/bash.bashrc -> ~/.bashrc
交互式 shell 顾名思义就是用来和用户交互的,提供了命令提示符可以输入命令。
该模式下会存在一个叫 PS1 的环境变量,如果还不是 login shell 的则会去 source /etc/bash.bashrc 和 ~/.bashrc 文件
non-interactive shell 则一般是通过 bash -c “CMD” 来执行的 bash.
不会执行任何 rc 配置
区别 | login | non-login |
---|---|---|
interactive | login 会加载 /etc/profile 和 ~/.profile ,interactive 会存在 PS1 变量 | 在终端中手动启动 bash, non-login 不会执行 profile,执行 /etc/bashrc 和 ~/.bashrc |
non-interactive | login 会执行 profile ,non-interactive 不会执行 rc | bash -c “CMD” 执行,不会执行 profile ,也不会执行 rc |
profile 是某个用户唯一的用来设置环境变量的地方,因为可能有多个 shell 比如 bash 或者 zsh,但环境变量只需要统一初始化就行,这就是 profile 的作用
用来给 bash 做初始化的,bash 的代码补全,bash 的别名,bash 的颜色,以此类推也就还会有 shrc, zshrc
通常一个好的做法是
/etc/profile
是配置所有用户的~/.profile
中配置和 bash 命令不是直接相关的内容,比如环境变量等等~/.bashrc
中配置和交互式命令行相关的配置,比如命令自动补全,EDITOR 变量,bash aliases 别名等等~/.bash_profile
可以当做 ~/.profile
使用,只会被 bash 读取上一篇写 Bash 的文章 ,只是介绍了 terminal 下面的一些快捷键,并没有对 Bash 和 Shell 本身展开多少的介绍,因此正好趁此机会。
sh(或者叫做 Shell Command Language), 是由 POSIX 标准 定义的一门语言。 它有很多实现(ksh88,dash…)等等, bash
同样可以认为是 sh 的一个实现。1
因为 sh
是一个规范,而不是实现,所以在大部分的 POSIX 系统上 /bin/sh
是一个 symlink。比如在 Debian/Ubtuntu 中, sh
便指向 dash
.
bash
在创造初期是作为 sh
兼容的实现,但是随着时间发展,延伸出了很多扩展功能。而许多新功能改变了原来 POSIX shell 的设计。因此, bash
自身并不是正宗的 POSIX shell,而是 POSIX shell 的一种方言。
bash
支持 --posix
模式,用来兼容 POSIX shell。很长一段时间来, 在 GNU/Linux 系统中 /bin/sh
都指向 /bin/bash
,久而久之人们便忘记了这两者之间的区别。
一些非常著名的系统 /bin/sh
并不指向 /bin/bash
的例子(甚至在有些系统中 /bin/bash
甚至不存在):
sh
默认指向 dash
ash
来作为 sh 的实现pdksh
.到此,基本也就解释了 sh 和 bash 的重要区别,但是就目前的学习, bash 因为实现比较流行,几乎绝大部分 shell 脚本都交由 bash 解释执行,并且 bash 相比较于 sh,功能更加强大,语法更加简洁。
下面的命令和使用基于 bash。
因为 Shell 似乎是各 UNIX 系统之间通用的功能,并且经过了 POSIX 的标准化。因此,Shell 脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用 Shell 脚本是基于:
但是,考虑到 Shell 脚本的命令限制和效率问题,下列情况一般不使用 Shell:
如果你的应用符合上面的任意一条,那么就考虑一下更强大的语言吧——或许是 Perl、Python、Ruby——或者是编译语言比如 C/C++,或者是 Java。
#!/bin/bash
echo "Hello World !"
#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行。通常这一行被称作 Shebang。常见的有 #!/bin/bash
是用 Bash Shell, #!/bin/sh
使用 Bourne Shell。
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
Linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里 要用 ./test.sh
告诉系统说,就在当前目录找。
再一个稍微复杂一点的脚本:
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"
学习每一门编程语言都是从变量开始。
变量定义时,变量名不加 $,如
variableName="value"
变量名和等号之间不能有空格,变量名需遵循规则:
使用定义过的变量,在变量名之前添加美元符号 ${variableName}
yourName="einverne"
echo $yourName
echo ${yourName}
花括号是可选的,用来识别变量边界,建议每次都加上。
不加美元符号,直接重新赋值即可
yourName="einverne"
echo "your name is ${yourName} !!"
yourName="Ein Verne"
echo "your name is ${yourName} !!"
yourName="einverne"
echo "your name is ${yourName} !!"
readonly yourName
yourName="Ein Verne"
会发生错误,
your name is einverne !!
./demo.sh: line 25: yourName: readonly variable
使用 unset 命令删除变量
unset variableName
unset 不能删除只读变量
同时存在三种变量:
有 shell 程序设置的特殊变量
变量 | 含义 |
---|---|
$$ |
当前 Shell 进程 ID , PID |
$0 | 当前脚本的文件名 |
$n | 传递给脚本或函数的参数, n 是数字,表示第几个参数 |
$# | 传递给脚本或函数的参数个数 |
$* | 传递给脚本的所有参数 |
$@ | 传递给脚本的所有参数,被双引号包围稍有不同 |
$? | 上个命令的退出状态,或者函数的返回值 |
$*
和 $@
都表示传递给函数或脚本的所有参数,不被双引号 (“ “) 包含时,都以$1
$2
… $n
的形式输出所有参数。
但是当它们被双引号 (“ “) 包含时,$*
会将所有的参数作为一个整体,以$1 $2 … $n
的形式输出所有参数;$@
会将各个参数分开,以$1
$2
… $n
的形式输出所有参数。
测试脚本
set -o nounset # Treat unset variables as an error
echo "File name is $0"
echo "First parameter : $1"
echo "First parameter : $2"
echo "This script have $# parameters"
echo "They are $*"
echo "They are $@"
for var in "$*"
do
echo "${var}"
done
for var in "$@"
do
echo "${var}"
done
结果:
./demo.sh p1 p2
File name is ./demo.sh
First parameter : p1
First parameter : p2
This script have 2 parameters
They are p1 p2
They are p1 p2
p1 p2
p1
p2
转义表
转义字符 | 含义 |
---|---|
\ | 反斜杠 |
\a | 报警 |
\b | 退格 |
\f | 换页,将当前位置移到下页开头 |
\n | 换行 |
\r | 回车 |
\t | Tab |
\v | 垂直制表符 |
使用 echo -e "value is ${variableName} \n"
中的 -e
选项来进行转义,否则会原样输出。
echo 的 -E
禁止转义,默认不转义, -n
来禁止插入换行符。
将输出结果保存到变量中,在合适的地方使用
Data=`date`
echo "Date is ${Date}"
根据变量的状态,来改变变量的值
形式 | 说明 |
---|---|
${var} | 变量原本值 |
${var:-word} | 如果变量 var 为空或已被删除 (unset),那么返回 word,但不改变 var 的值。 |
${var:=word} | 如果变量 var 为空或已被删除 (unset),那么返回 word,并将 var 的值设置为 word。 |
${var:?message} | 如果变量 var 为空或已被删除 (unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。 若此替换出现在 Shell 脚本中,那么脚本将停止运行。 |
${var:+word} | 如果变量 var 被定义,那么返回 word,但不改变 var 的值。 |
#!/bin/sh
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a is equal to b"
fi
if [ $a != $b ]
then
echo "a is not equal to b"
fi
基本解释
2+2
是不对的,必须写成 2 + 2
[$a==$b]
是错误的,必须写成 [ $a == $b ]
关系运算符只能用于数字,或者字符串中只能包含数字
运算符 | 说明 |
---|---|
-eq | 相等返回 true |
-ne | 不相等返回 true |
-gt | great than 返回 true |
-lt | less than 返回 true |
-ge | great or equal than , 返回 true |
-le | less or equal than , 返回 true |
运算符 | 说明 |
---|---|
! | 非 |
-o | 或 or |
-a | 与 and |
# 单引号中的任何字符都原样输出
# 单引号中不能出现单引号
echo '${hi}: "einverne"\t\n\r'
# 双引号可以有变量
# 双引号可以出现转义字符
yourName='einverne'
hi="hi, ${yourName} !\n"
# 字符长度
echo "your name length: ${#yourName}"
echo "first two character of your name ${yourName:0:2}"
text="0123456789-0123456789"
echo $text
echo '${text:position}' "${text:1}"
echo '${text:position:length}' "${text:1:3}"
# 从变量 $string 的开头,删除最短匹配 $substring 的子串
echo '${text#01}' "${text#01}"
# 从变量 $string 的开头,删除最长匹配 $substring 的子串
echo '${text##01}' "${text##01}"
# 从变量 $string 的结尾,删除最短匹配 $substring 的子串
echo '${text%89}' "${text%89}"
# 从变量 $string 的结尾,删除最长匹配 $substring 的子串
echo '${text%%89}' "${text%%89}"
# 使用 $replacement, 来代替第一个匹配的 $substring
echo '${text/substring/replacement} ${text/01/ab}' "${text/01/ab}"
# 使用 $replacement, 代替所有匹配的 $substring
echo '${text//substring/replacement} ${text//01/ab}' "${text//01/ab}"
# 如果 $string 的前缀匹配 $substring, 那么就用 $replacement 来代替匹配到的 $substring
echo '${text/#substring/replacement} ${text/#01/ab}' "${text/#01/ab}"
# 如果 $string 的后缀匹配 $substring, 那么就用 $replacement 来代替匹配到的 $substring
echo '${text/%substring/replacement} ${text/%01/ab}' "${text/%01/ab}"
array_name=(value0 value1 value2 value3)
# 或者
array_name=(
value0
value1
value2
value3
)
# 或者
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
# 读取数组
valuen=${array_name[2]}
# 获取所有元素
${array_name[*]}
${array_name[@]}
# 长度
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
条件判断有两种格式,test EXPRESSION
和 [ EXPRESSION ]
, test 判断语句实际应用比较少, []
判断应用比较广。
基本格式
test EXPRESSION
test 是关键词,表判断, EXPRESSION 被判断语句。
表达式 | 说明 |
---|---|
test -d File | 判断 File 是不是目录 |
test -f File | 判断 File 是不是普通文件 |
test -L File | 判断 File 是不是符号链接 |
test -r File | 判断 File 是不是可读 |
test -s File | 判断 File 是不是文件长度大于 0、非空 |
test -w File | 判断 File 是不是可写 |
test -u File | 判断 File 是不是有 suid 位设置 |
test -x File | 判断 File 是不是可执行 |
格式
[ EXPRESSION ]
中括号左右必须要有空格隔开
表达式 | 说明 |
---|---|
[ (expr) ] | expr 为真 |
[ !expr ] | expr 为假 |
[ expr1 -a expr2 ] | expr1 和 expr2 同时为真 |
[ expr1 -o expr2 ] | expr1 或 expr2 为真 |
[ -n string ] | string 的长度不为 0 |
[ -z string ] | string 的长度为 0 |
[ string1 = string2 ] | 两个字符串 string1 和 string2 相等 |
[ string1 != string2 ] | 两个字符串 string1 和 string2 不等 |
[ integer1 -eq integer2 ] | 两个 integer1 和 integer2 整数相等 |
[ integer1 -ne integer2 ] | integer1 不等于 integer2 |
[ integer1 -ge integer2 ] | integer1 大于或等于 integer2 |
[ integer1 -gt integer2 ] | integer1 大于 integer2 |
[ integer1 -le integer2 ] | integer1 小于或等于 integer2 |
[ integer1 -lt integer2 ] | integer1 小于 integer2 |
[ file1 -ef file2 ] | 文件 file1 和 file2 有相同的 device 和 inode 数目 |
[ file1 -nt file2 ] | file1 的修改事件早于 file2 |
[ file1 -ot file2 ] | file1 的修改事件晚于 file2 |
[ -b file ] | file 是块设备 |
[ -c file ] | file 是字符设备 |
[ -d file ] | file 是文件夹 |
[ -e file ] | file 是存在 |
[ -f file ] | file 是普通文件 |
[ -g file ] | file 存在,且有 group-ID |
[ -G file ] | file 存在,且 group-ID 是有效的 |
[ -h file ] | file 存在,且是一个硬链接 |
[ -L file ] | file 存在,且是一个软链接 |
[ -r file ] | file 存在,且可读 |
[ -w file ] | file 存在,且可写 |
[ -x file ] | file 存在,且可执行 |
下面是一些例子:
if [ expression ]
then
Statement(s) to be executed if expression is true
fi
if [ expression ]
then
Statement(s) to be executed if expression is true
else
Statement(s) to be executed if expression is not true
fi
# 更复杂
if [ expression 1 ]
then
Statement(s) to be executed if expression 1 is true
elif [ expression 2 ]
then
Statement(s) to be executed if expression 2 is true
elif [ expression 3 ]
then
Statement(s) to be executed if expression 3 is true
else
Statement(s) to be executed if no expression is true
fi
CASE 语句为多选择语句,CASE 语句匹配一个值与一个模式,如果匹配成功就执行相应的命令。
echo 'Input a number between 1 to 4'
echo 'Your number is:\c'
read aNum
case $aNum in
1) echo 'You select 1'
;;
2) echo 'You select 2'
;;
3) echo 'You select 3'
;;
4) echo 'You select 4'
;;
*) echo 'You do not select a number between 1 to 4'
;;
esac
for 变量 in 列表
do
command1
command2
...
commandN
done
举例
sum=0
for ((i=1; i < 10; i++))
do
((sum=$sum+$i))
done
echo "sum=$sum"
while 语句
while condition
do
Statement(s) to be executed if command is true
done
举例
i=0
sum=0
while [ $i -lt 10 ]
do
(( i++ ))
(( sum=$sum+$i ))
echo "i=$i"
done
echo "sum=$sum"
until 语句
until condition
command 1
...
done
举例
i=0
sum=0
until [ $i -ge 10 ]
do
(( i++ ))
(( sum=$sum+$i ))
echo "i=$i"
done
echo "sum=$sum"
循环当中有 break 调出循环,和 continue 跳过本次循环这两个关键字,和其他编程语言一样,就不多说了。
function function_name () {
list of commands
[ return value ]
}