Openwrt 接口及基本设置

在上一篇中讲了如何刷 Openwrt,这一篇主要讲一些 Openwrt 的东西,以及配置相关的内容。我有一个主路由器,设置分配的局域网地址为 192.168.1.x,给内网中分配的地址也是 192.168.1.x 开头。

但是 Openwrt 默认为 AP 模式,我想要从主路由器 LAN 口连出到新的这个 Openwrt 路由器上,那么便得设置 Openwrt 路由器为 Router 模式以便于级联。

在设置路由器模式之前先来看看这几个接口,否则怎么都不会明白怎么配置的。

br-lan, eth0, eth0.1

Openwrt 的接口名字太多,最早接触路由器的时候只知道 WLAN 口,LAN 口,后来接触 Linux 才慢慢知道 eth0, lo 等等接口,但是在 Openwrt 上接口中突然冒出来一堆看着名字熟悉,却不知道什么作用的接口。今天配置 LAN ,WAN 口时还差点把 MR12U 搞砖,幸亏昨天刷了不死 boot。

可以使用 ifconfig 来查看设备,常见的几个端口:

  • lo 虚拟设备端口,自身回环设备,一般指向 127.0.0.1
  • ra0 rai0 成对出现,无线设备,对应各自的 SSID,分别是 2.4G 和 5G
  • pppoe-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。

下面张图比较直观:

openwrt-interface

更改内网地址

LAN 是设置局域网内的相关属性,可以设置内网的 IP,桥接的端口。比如我们默认使用 192.168.1.1 访问,可以修改为 192.168.9.1,生效后内网分配的 ip 网段就会变成 192.168.9.x 。LAN 口的协议为【静态地址】。下一次访问路由器管理页面就需要使用 192.168.9.1 了。

Openwrt 修改 LuCI 语言

System->Software->在 Filter 栏里面输入 -zh-cn 点击搜索

找到 luci-i18n-base-zh-cn 点击前面的安装。然后去设置语言即可。

设置路由器模式

路由器模式也就是最常见的无线模式,通过有线连接路由器 WAN 口至互联网,并发射无线提供局域网络。由于 OpenWrt 默认只有 LAN 接口,我们需要添加 WAN 接口。

Openwrt interface screenshot-area-2017-03-08-212320

Openwrt morning 配置只有上述图片的 LAN 口,下面的 WAN 口通过如下方法添加。

点击下方的“添加新接口”

screenshot-area-2017-03-08-212349

为了便于区分,接口名称建议使用 WAN。按照网络接入类型,选择 DHCP(从外网自动获取 ip 地址),静态 ip 或者 PPPoE 拨号即可。其它设置如图,请勿选择“在多个接口上创建桥接”,最后点击提交。

screenshot-area-2017-03-08-212405

提交后选择刚刚创建的 WAN 接口,点击“防火墙设置”,选择 WAN 并保存即可。

screenshot-area-2017-03-08-212422

这时需要再次回到 LAN 接口,点击编辑。

screenshot-area-2017-03-08-212808

选择“物理设置”,确保“桥接接口”为选中,接口中不选中“以太网适配器”。确认后保存并应用,至此所有配置完成,连接网线即可使用。

设置有线中继模式

一种需求是将有线网络转为无线 WiFi,使得 WiFi 网络和其他原来在有线网络中的设备在同一子网中,比如说一级路由网段 192.168.2.X,然后添加无线路由器分享 WiFi,使得 WiFi 上的设备网段依然是 192.168.2.X,不产生新子网,没有 NAT,一级路由下面设备可以访问二级路由下任何设备不需要做端口转发。

有线中继的方式下,二级路由不会 DHCP,并且网段相同。如果要实现这种模式,OpenWrt 需要将无线网卡接口和 WAN 划分到一起,路由器 Master 模式 + 无线 AP.

  • 设置无线 wlan0, 接入点 AP 模式,并且将网络和 WAN 划到一起。

此时连接 WiFi,会发现客户端通过一级路由分配 IP 地址,网关是上一级路由的网关。

设置有线桥接模式

无线桥接模式

有些时候并没有有线网络可以连接到 OpenWrt 路由器 WAN 口,但是有 WiFi 可以连接,那么可以将路由器转变成一个 WiFi 信号放大器,扩展 WiFi 信号覆盖范围。

无线中继模式使用无线网络接入互联网,并生成一个新的 SSID无线桥接模式无需更改有线连接接口设置。打开无线接口设置,点击搜索。在自动弹出的设置页面中,填写上级无线密码。新网络的名称使用默认 WWAN 即可。防火墙区域选择 WAN,在这里请勿选择“重置无线配置”。在保存并应用后就完成了所有设置。

假如有一台主路由已经接入了互联网(内网网段是 192.168.2.1/24),现在要在一台从路由 (OpenWrt) 上无线桥接到主路由。

  • 在二级 OpenWrt 路由器上安装 luci-proto-relayrelayd
  • 在 “网络” -> “接口” 中修改 LAN 口地址,与主路由不在同一个网段,比如主路由在 192.168.2.1/24,那么在二级路由中就不用这个网段,而配置 192.168.199.1/24
  • 关闭 LAN 口 DHCP
  • 设置 OpenWrt 桥接模式,在无线接口中,搜索(扫描),找到主路由 Wifi 名字,输入主路由的密码连接,在接口配置中使用客户端模式,网络选择 wwan. 然后生成新的 wwan 接口。点到接口页面,能看到 WWAN 获取了主路由分配的 IP。
  • 设备无线网络,从路由器发送 AP,设置 ESSID(起一个无线 Wifi 的名字), 模式选择接入点 AP,网络选 lan,在安全页面设定 WiFi 密码
  • 设置后就连接新的无线网络就实现了 OpenWrt 上的无线桥接。

无线 AP (中继)模式

无线 AP(接入点) 模式多应用于公共场所,所有无线设备将被桥接至以太网接口,由上级网关负责 DHCP。在设置完成后 AP 所在路由器将无法访问。

首先打开 LAN 接口或者 WAN 接口,选择“物理设置”,确保“桥接接口”为选中。在下方接口选中“以太网适配器”以及“无线网络”,保存并应用即可。

至于无线加密设置以及 DHCP 设置较为简单,自行在“网络”分类下查找即可。

桥接中继模式的区别

总结性的归纳一下,中继和桥接都可以扩大原来的信号范围。

  • 无线中继通过接受信号,再发送信号的方式,可以自己设定是否提供 DHCP NAT
  • 而无线桥接模式不参与

    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,提供一个自己的局域网。

中继和桥接模式都可以通过无线方式扩大信号,区别在于扩大后的无线信号名字不一样。

reference


2017-03-09 openwrt , linux , opkg , router

TP LINK MR12U 刷 openwrt

今天翻箱倒柜竟然找出了我的 TP-LINK MR12U,很早之前因为 3G 上网卡而买的便携式路由,突然脑袋一热,干嘛不试试刷个 Openwrt 呢。记得当时是没有支持的,但是一搜竟然发现了 Openwrt 有官方支持了。于是开始动手。

这里主要记录一下 MR12U v1.0 版本的过程,但是感觉其他路由器异曲同工,掌握了一种方法其他路由器也是类似的原理。刷机的过程有风险,因此一定要做好充分的调查和心理准备。很早之前写过一篇文章讲防止 Android 刷机变砖 ,我利用其中用到的方法一直刷机至今。说到底,终究要知道自己做的每一步是什么含义,出现的每一个术语是什么含义。

我一直坚信着“授人以鱼不如授人以渔”的理念,因此我在文中会把我参考的所有文章以及想法过程都记录下来,以便于在以后刷其他路由器的时候能够更加快速,并且如果有其他人能看到也能更加明白。

Openwrt

首先什么是 Openwrt,Openwrt 是一个适合嵌入式设备的 Linux 发行版 1,相对原厂固件而言,OpenWrt 不是一个单一静态的固件,而是提供了一个可添加软件包的可写的文件系统。这使用户可以自由的选择应用程序和配置,而不必受设备提供商的限制,并且可以使用一些适合某方面应用的软件包来定制你的设备。对于开发者来说,OpenWrt 是一个框架,开发者不必麻烦的构建整个固件就能得到想要的应用程序;对于用户来说,这意味着完全定制的能力,与以往不同的方式使用设备,OPKG 包含超过 3500 个软件。

默认使用 LuCI 作为 web 交互界面。

因为其强大的扩展性,所以能用 Linux 做到的事情,Openwrt 几乎都能做到,而如今生活在墙内,路由器很重要的一个功能便是翻墙,结合 Shadowsocks,pdnsd 等等 Openwrt 可以做到透明代理。去除这个硬性需求外,其他比如:

  • 脱机下载
  • SMB
  • SSH
  • 单线多播
  • 远程视频监控
  • 去广告,屏蔽恶意域名

甚至定时关 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。

刷机过程:

  1. 开机状态下按住路由器 reset 按 5 秒,重置路由器。
  2. 连上 wifi,进入 192.168.1.1,系统工具 -> 软件升级,刷入 openwr-ar71xx-generic-tl-mr11u-v2-squashfs-factory.bin,等上几分钟
  3. 用 lan 线连接路由器和电脑,会发现已经变成 openwrt 的界面了,在后台修改密码,打开无线功能。
  4. 如果需要刷 不死 u-boot , 可以参考原贴.
  5. 具体过程总结,使用 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 地址,后面会用到。
    
  6. 上传 breed-ar9331-mr12u.bin 使用 scp breed-ar9331-mr12u.bin root@192.168.1.1:/tmp/
  7. 再使用 ssh 连山路由器,使用如下命令

     cd /tmp
     mtd write breed-ar9331-mr12u.bin u-boot
     # 成功后 reboot 重启路由器即可。
    
  8. 如果这一步出现如下信息,是使用了 Openwrt 官方的固件,默认是锁了 u-boot 的。

     Could not open mtd device: u-boot
     Can't open device for writing!
    
  9. 当前的 Openwrt 锁了 u-boot 分区,需要刷入一个未锁分区的 Openwrt 固件,可以上论坛找一下,将解锁分区的固件上传到 /tmp 目录,使用 mtd 命令写入固件 firmware 分区。

    cd /tmp mtd write openwr-ar71xx-generic-tl-mr11u-v2-squashfs-factory.bin firmware

  10. 刷入了 u-boot 分区后可按照如下步骤进入 u-boot 控制台。
  11. 路由器和电脑连接,在关机状态下,使用工具按住 reset 按钮不放,打开路由器开关,过一会儿看到蓝色灯亮一下,再过一会儿看到蓝色灯闪 4 下,松开 reset 按钮,在浏览器输入 192.168.1.1 进入 u-boot 界面。
  12. 修改 MAC 地址, u-boot 会将 MAC 地址重置,需要将 MAC 地址还原回来,不然有些功能无法使用,比如无线功能,将之前备份好的 MAC 地址放入 TP-LINK 设置下的 MAC 地址位置。
  13. 如果想要刷入新固件,直接在 u-boot 中固件更新刷入新固件即可。

reference


2017-03-08 openwrt , linux , router

每天学习一个命令:scp 命令行下远程主机之间拷贝文件

昨天刷 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/

外延

reference


2017-03-08 linux , scp , file , vps , command

使用 Cron 定时重启 Openwrt 路由器

最近了解了一下 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 目录下最近修改的文件,并将该文件修改的时间戳作为开机的初始时间。

cron 语法

一个 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 ,则表示每隔两分钟

而空格分割的六个域分别表示:

  • 第 1 列分钟,取值范围 0~59
  • 第 2 列小时 0~23(0 表示子夜)
  • 第 3 列日 1~31
  • 第 4 列月 1~12
  • 第 5 列星期 0~7(0 和 7 表示星期天)
  • 第 6 列要运行的命令

注意事项:

  1. 重复格式 /2 表示没两分钟执行一次 或者 /10 表示每 10 分钟执行一次,这样的语法格式并不是被所有系统支持。
  2. 具体某一天的指定,可以由第三项(month day)和第五项(weekday)指定,如果两项都被设定,那么 cron 都会执行。

更多具体关于 crontab 的内容,可以参考 WizNote


2017-03-07 Linux , crontab , Openwrt , Router

整站备份工具 Httrack

根据官方的介绍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 动态网站却无能为力,镜像后容易都是内容。


2017-03-06 Linux , Httrack , backup

解决 Linux Mint 18.1 安装 NVIDIA 驱动后黑屏

Mint 下有一个 Driver Manager 驱动管理,手贱升级了一下到 378,下载,自动安装,重启倒是没有什么但问题,最关键的是,第二次重启的时候直接黑屏,之前也遇到过一回,记忆中是修改了 /etc/X11/xorg.conf 才修复这个问题,没想到这一次又遇到了这个问题。但是无论我怎么修复笔记本始终黑屏在,开机闪过 Linux Mint 的 logo 之后始终无法启动 x server。

第一步尝试卸载NVIDIA驱动

凭借这记忆中的印象,在启动登录之后 Ctrl+Alt+1~7 1~7中的任意一个键,进入 tty 终端,用 root 用户登录,成功后在其中 apt purge nvidia* 移除所有 NVIDIA 驱动。然后重启。

第二部修改 xorg.conf

在第一步尝试不成功之后,尝试修改 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

尝试重新安装 NVIDIA 驱动

甚至在终端下安装了 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

linux_trovalds_fuck_nvidia

reference


2017-03-05 linux , nvidia , linux-mint

每天学习一个命令:crontab 定时任务

通过 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 是 /etc/crontab。这个文件有独特的语法(在下面讨论),其中定义的每个作业根据它自己的时间表(比如每小时两次或每天一次)作为指定的用户运行。使用 /etc/crontab 调度各种管理和维护任务。
  • 还可以在 /etc/cron.d 目录中维护一组 crontab。通过创建 crontab,按照逻辑对属于某一子系统的命令进行分组。例如,PHP 5 编程语言的包在 /etc/cron.d 中安装一个名为 php5 的 crontab,它会定期清除不使用的会话。/etc/cron.d 中的文件采用与 /etc/crontab 相同的语法,每个作业按照自己的时间表并作为特定的用户运行。
  • 还可以把 shell 脚本直接放在 /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

而空格分割的六个域分别表示:

  • 第 1 列分钟,取值范围 0~59
  • 第 2 列小时 0~23(0 表示子夜)
  • 第 3 列日 1~31
  • 第 4 列月 1~12
  • 第 5 列星期 0~7(0 和 7 表示星期天)
  • 第 6 列要运行的命令

注意事项:

  1. 重复格式 /2 表示没两分钟执行一次 或者 /10 表示每 10 分钟执行一次,这样的语法格式并不是被所有系统支持。
  2. 具体某一天的指定,可以由第三项(month day)和第五项(weekday)指定,如果两项都被设定,那么 cron 都会执行。

创建一个新的 crontab 文件

向 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)。

列出 crontab 文件

使用 -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 文件

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

调试 Debug Cron 任务

cron 在 /var/log/syslog 中有相关日志,可以使用 tailf /var/log/syslog | grep cron 来查看 cron 运行日志。或者使用 sudo /etc/init.d/cron status 来查看 cron 运行状态。使用 restart 或者 start 来重启或者 启动 cron。

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 descriptor
  • 1 标准输出描述符

可以参考 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 @reboot

如果想要系统在重启之后执行某个脚本,通过 crontab 可以实现:

编辑 crontab:

crontab -e

然后加入:

@reboot /usr/local/xxxxx
@reboot /usr/local/xxxxx.sh

reference


2017-03-05 linux , command , cron , crontab , scheduler , period

IO 重定向

上一篇讲了 shell 脚本的基本语法,然后这篇补一补标准输入输出重定向命令,以及管道命令。

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为 0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为 1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为 2,Unix程序会向stderr流中写入错误信息。

管道命令

管道命令的操作符是”|”, 它将前一个命令的输出给下一个命令作为输入。

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 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。

如果希望屏蔽 stdout 和 stderr,可以这样写:

$ command > /dev/null 2>&1

reference


2017-03-02 Linux , Shell , Bash

理解 bashrc profile 优先级及区别

在类 Unix 系统中,我们通常会配置 Shell 的 ~/.bashrc 或者 /etc/profile 来设置用户的工作环境。一般系统可能会有

/etc/profile
/etc/bashrc
/etc/bash.bashrc

~/.bashrc
~/.profile

在理解这些文件之前,需要了解 Shell 不同的登录方式,login 登录和 interactive 模式。

关于 Shell 的历史和分类可以参考之前的文章

shell 不同启动方式

login shell

“login shell” 代表用户登入,比如使用 “su -“ 命令,或者用 ssh 连接到某一个服务器上,都会使用该用户默认 shell 启动 login shell 模式。

该模式下的 shell 会去自动执行 /etc/profile~/.profile 文件,但不会执行任何的 bashrc 文件,所以一般再 /etc/profile 或者 ~/.profile 里我们会手动去 source bashrc 文件。

启动场景:

  • SSH 登录
  • sudo su -
  • 或者开启任何默认启动 bash 的终端

此时配置加载顺序

/etc/profile -> /etc/bash.bashrc
~/.profile -> ~/.bashrc

no-login shell

而 no-login shell 的情况是我们在终端下直接输入 bash 或者 bash -c “CMD” 来启动的 shell.

该模式下是不会自动去运行任何的 profile 文件。

/etc/bash.bashrc -> ~/.bashrc

interactive shell

交互式 shell 顾名思义就是用来和用户交互的,提供了命令提示符可以输入命令。

该模式下会存在一个叫 PS1 的环境变量,如果还不是 login shell 的则会去 source /etc/bash.bashrc 和 ~/.bashrc 文件

non-interactive

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

profile 是某个用户唯一的用来设置环境变量的地方,因为可能有多个 shell 比如 bash 或者 zsh,但环境变量只需要统一初始化就行,这就是 profile 的作用

bashrc

用来给 bash 做初始化的,bash 的代码补全,bash 的别名,bash 的颜色,以此类推也就还会有 shrc, zshrc

通常一个好的做法是

  • /etc/profile 是配置所有用户的
  • ~/.profile 中配置和 bash 命令不是直接相关的内容,比如环境变量等等
  • ~/.bashrc 中配置和交互式命令行相关的配置,比如命令自动补全,EDITOR 变量,bash aliases 别名等等
  • ~/.bash_profile 可以当做 ~/.profile 使用,只会被 bash 读取

bash load order

reference


2017-03-02 linux , shell , bash , bashrc

Shell/Bash script 学习笔记

上一篇写 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 甚至不存在):

  1. Debian/Ubuntu 系统中 sh 默认指向 dash
  2. BusyBox 使用 ash 来作为 sh 的实现
  3. BSDs, OpenBSD 使用 pdksh .

到此,基本也就解释了 sh 和 bash 的重要区别,但是就目前的学习, bash 因为实现比较流行,几乎绝大部分 shell 脚本都交由 bash 解释执行,并且 bash 相比较于 sh,功能更加强大,语法更加简洁。

Shell 实现

  • sh sh 由 Steve Bourne 开发,是 Bourne Shell 的缩写,sh 是 Unix 标准默认的 shell。
  • ash shell 是由 Kenneth Almquist 编写的,Linux 中占用系统资源最少的一个小 shell,它只包含 24 个内部命令。
  • csh 它由以 William Joy 为代表的共计 47 位作者编成。它产生于 Unix 第六版 /bin/sh, 是 Bourne Shell 的前身。
  • ksh 是 Korn shell 的缩写,由 David G. Korn 编写。该 shell 最大的优点是几乎和商业发行版的 ksh 完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。
  • bash 由 Brian Fox 和 Chet Ramey 共同完成,是 BourneAgain Shell 的缩写。

下面的命令和使用基于 bash。

Shell 适用

因为 Shell 似乎是各 UNIX 系统之间通用的功能,并且经过了 POSIX 的标准化。因此,Shell 脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用 Shell 脚本是基于:

  • 简单性:Shell 是一个高级语言;通过它,你可以简洁地表达复杂的操作。
  • 可移植性:使用 POSIX 所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。
  • 开发容易:可以在短时间内完成一个功能强大又妤用的脚本。

但是,考虑到 Shell 脚本的命令限制和效率问题,下列情况一般不使用 Shell:

  • 资源密集型的任务,尤其在需要考虑效率时(比如排序,hash 等等)。
  • 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用 C++ 或 FORTRAN 来处理)。
  • 有跨平台(操作系统)移植需求(一般使用 C 或 Java)。
  • 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。
  • 对于影响系统全局性的关键任务应用。
  • 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。
  • 项目由连串的依赖的各个部分组成。
  • 需要大规模的文件操作。
  • 需要多维数组的支持。
  • 需要数据结构的支持,比如链表或数等数据结构。
  • 需要产生或操作图形化界面 GUI。
  • 需要直接操作系统硬件。
  • 需要 I/O 或 socket 接口。
  • 需要使用库或者遗留下来的老代码的接口。
  • 私人的、闭源的应用

如果你的应用符合上面的任意一条,那么就考虑一下更强大的语言吧——或许是 Perl、Python、Ruby——或者是编译语言比如 C/C++,或者是 Java。

初识 Shell

#!/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"

变量名和等号之间不能有空格,变量名需遵循规则:

  • 首字符必须为 [^a-zA-z]
  • 中间不能有空格,可是有下划线 _
  • 不能使用标点
  • 不能使用 bash 关键字, help 命令可查看保留关键字

引用

使用定义过的变量,在变量名之前添加美元符号 ${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 不能删除只读变量

变量类型

同时存在三种变量:

  1. 局部变量,字脚本或者命令中定义,仅在当前 shell 实例中有效,其他启动的程序不能访问
  2. 环境变量,所有陈旭,包括 shell 启动的程序都能访问,有些程序需要依赖环境变量 PATH 来保证其正常运行
  3. shell 变量

有 shell 程序设置的特殊变量

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]}

条件语句

IF

条件判断有两种格式,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 语句为多选择语句,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

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 语句

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 语句

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 ]
}

reference


2017-03-01 linux , shell , bash , sh , zsh

电子书

最近文章

  • 时隔 5 年再安装 NextCloud 时隔多年,我再安装了一次 [[NextCloud]],很早之前 就在我的 QNAP TS 453B mini 上安装并使用了多年,这两年也一直在跟随着官方的版本升级 ,但是 QNAP 毕竟在局域网内,虽然可以使用 Tailscale,ZeroTier 等等工具来组件局域网,但毕竟还是不方便,最近入手一台还不错的 VPS,所以想着再搭建一个公有 IP 的 NextCloud,一方面备份一下自己的相册,另一方面也补足一下我自己使用 Syncthing时没有在线预览的页面,导致常常有些时候想访问自己的笔记而找不到。
  • 使用 SyncTV 异地远程一起看视频 因为和女朋友异地,之前我们都一直使用 [[Plex]] 来同步看视频,基本上可以做到秒级别的共享观看。今天看 GitHub 的时候发现一款叫做 SyncTV 的应用,也是可以通过同步的方式了共享观看视频流,那这样就可以一起看视频,看直播了。原来 Plex 的因为放在了一台欧洲的服务器上,在国内播放的时候总是会出现卡顿,一来是带宽限制,二来是经常发现有转码,所以常常需要我预先转码为低码率的视频才能流畅播放,出现的这个 SyncTV ,我可以搭配一台地理位置比较好的,比如日本的机器,然后通过代理的方式播放,不知道会不会提升一下使用体验。
  • 《被讨厌的勇气》读书笔记 怎么知道的这一本书
  • 修复 macOS 时区和时间错误 今天 macOS 上又发生了一个奇怪的问题,昨天晚上因为没有连接充电线,所以导致 MacBook 晚上自动关机了,看起来是因为没电自动关机了,但是早上看的时候还有一点点电,白天的时候没有任何操作就直接充电到满,但是晚一些使用的时候就发现系统的时间不太对了,在系统设置中强制同步系统时间,但没有用处,每一次同步都比当前的时间慢了一天和几个小时,甚至连分钟都对不上。于是就开始了一系列的修复过程。
  • 为播客爱好者制作的工具 Podwise Podwise 是一个针对播客的笔记软件,过去很长的一段时间里面都在寻找能够给播客记笔记的应用,之前在移动端发现了一个 [[Snipd]],可以在听到的时候,记录下播客中的精彩瞬间,还可以做笔记。但今天发现的这一款 Podwise 是基于网页的,可以在网页中播放,并且 Podwise 提供了语音转写的文字稿。