Paul Brown 发布的文章

我有一个这样的电脑棒(图1),我把它用作通用服务器。它很小且安静,由于它是基于 x86 架构的,因此我为我的打印机安装驱动没有任何问题,而且这就是它大多数时候干的事:与客厅的共享打印机和扫描仪通信。

一个英特尔电脑棒。欧元硬币大小。

大多数时候它都是闲置的,尤其是当我们外出时,因此我认为用它作监视系统是个好主意。该设备没有自带的摄像头,也不需要一直监视。我也不想手动启动图像捕获,因为这样就意味着在出门前必须通过 SSH 登录,并在 shell 中编写命令来启动该进程。

因此,我以为应该这么做:拿一个 USB 摄像头,然后只需插入它即可自动启动监视系统。如果这个电脑棒重启后发现连接了摄像头也启动监视系统就更加分了。

在先前的文章中,我们看到 systemd 服务既可以手动启动或停止,也可以在满足某些条件时启动或停止。这些条件不限于操作系统在启动或关机时序中达到某种状态,还可以在你插入新硬件或文件系统发生变化时进行。你可以通过将 Udev 规则与 systemd 服务结合起来实现。

有 Udev 支持的热插拔

Udev 规则位于 /etc/udev/rules 目录中,通常是由导致一个 动作 action 条件 conditions 赋值 assignments 的单行语句来描述。

有点神秘。让我们再解释一次:

通常,在 Udev 规则中,你会告诉 systemd 当设备连接时需要查看什么信息。例如,你可能想检查刚插入的设备的品牌和型号是否与你让 Udev 等待的设备的品牌和型号相对应。这些就是前面提到的“条件”。

然后,你可能想要更改一些内容,以便以后可以方便使用该设备。例如,更改设备的读写权限:如果插入 USB 打印机,你会希望用户能够从打印机读取信息(用户的打印应用程序需要知道其模型、制造商,以及是否准备好接受打印作业)并向其写入内容,即发送要打印的内容。更改设备的读写权限是通过你之前阅读的“赋值” 之一完成的。

最后,你可能希望系统在满足上述条件时执行某些动作,例如在插入某个外部硬盘时启动备份程序以复制重要文件。这就是上面提到的“动作”的例子。

了解这些之后, 来看看以下几点:

ACTION=="add", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="e207", 
SYMLINK+="mywebcam", TAG+="systemd", MODE="0666", ENV{SYSTEMD_WANTS}="webcam.service"

规则的第一部分,

ACTION=="add", SUBSYSTEM=="video4linux",  ATTRS{idVendor}=="03f0", 
ATTRS{idProduct}=="e207" [etc... ]

表明了执行你想让系统执行的其他动作之前设备必须满足的条件。设备必须被添加到(ACTION=="add")机器上,并且必须添加到 video4linux 子系统中。为了确保仅在插入正确的设备时才应用该规则,你必须确保 Udev 正确识别设备的制造商(ATTRS{idVendor}=="03f0")和型号(ATTRS{idProduct}=="e207")。

在本例中,我们讨论的是这个设备(图2):

这个试验使用的是 HP 的摄像头。

注意怎样用 == 来表示这是一个逻辑操作。你应该像这样阅读上面的简要规则:

如果添加了一个设备并且该设备由 video4linux 子系统控制,而且该设备的制造商编码是 03f0,型号是 e207,那么…

但是,你从哪里获取的这些信息?你在哪里找到触发事件的动作、制造商、型号等?你可要使用多个来源。你可以通过将摄像头插入机器并运行 lsusb 来获得 IdVendoridProduct

lsusb
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 003: ID 03f0:e207 Hewlett-Packard
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 04f2:b1bb Chicony Electronics Co., Ltd
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

我用的摄像头是 HP 的,你在上面的列表中只能看到一个 HP 设备。ID 提供了制造商和型号,它们以冒号(:)分隔。如果你有同一制造商的多个设备,不确定哪个是哪个设备,请拔下摄像头,再次运行 lsusb , 看看少了什么。

或者…

拔下摄像头,等待几秒钟,运行命令 udevadmin monitor --environment ,然后重新插入摄像头。当你使用的是HP摄像头时,你将看到:

udevadmin monitor --environment
UDEV  [35776.495221] add      /devices/pci0000:00/0000:00:1c.3/0000:04:00.0
    /usb3/3-1/3-1:1.0/input/input21/event11 (input) 
.MM_USBIFNUM=00 
ACTION=add 
BACKSPACE=guess 
DEVLINKS=/dev/input/by-path/pci-0000:04:00.0-usb-0:1:1.0-event 
     /dev/input/by-id/usb-Hewlett_Packard_HP_Webcam_HD_2300-event-if00 
DEVNAME=/dev/input/event11 
DEVPATH=/devices/pci0000:00/0000:00:1c.3/0000:04:00.0/
     usb3/3-1/3-1:1.0/input/input21/event11 
ID_BUS=usb 
ID_INPUT=1 
ID_INPUT_KEY=1 
ID_MODEL=HP_Webcam_HD_2300 
ID_MODEL_ENC=HPx20Webcamx20HDx202300 
ID_MODEL_ID=e207 
ID_PATH=pci-0000:04:00.0-usb-0:1:1.0 
ID_PATH_TAG=pci-0000_04_00_0-usb-0_1_1_0 
ID_REVISION=1020 
ID_SERIAL=Hewlett_Packard_HP_Webcam_HD_2300 
ID_TYPE=video 
ID_USB_DRIVER=uvcvideo 
ID_USB_INTERFACES=:0e0100:0e0200:010100:010200:030000: 
ID_USB_INTERFACE_NUM=00 
ID_VENDOR=Hewlett_Packard 
ID_VENDOR_ENC=Hewlettx20Packard 
ID_VENDOR_ID=03f0 
LIBINPUT_DEVICE_GROUP=3/3f0/e207:usb-0000:04:00.0-1/button 
MAJOR=13 
MINOR=75 
SEQNUM=3162 
SUBSYSTEM=input 
USEC_INITIALIZED=35776495065 
XKBLAYOUT=es 
XKBMODEL=pc105 
XKBOPTIONS= 
XKBVARIANT=

可能看起来有很多信息要处理,但是,看一下这个:列表前面的 ACTION 字段, 它告诉你刚刚发生了什么事件,即一个设备被添加到系统中。你还可以在其中几行中看到设备名称的拼写,因此可以非常确定它就是你要找的设备。输出里还显示了制造商的ID(ID_VENDOR_ID = 03f0)和型号(ID_VENDOR_ID = 03f0)。

这为你提供了规则条件部分需要的四个值中的三个。你可能也会想到它还给了你第四个,因为还有一行这样写道:

SUBSYSTEM=input

小心!尽管 USB 摄像头确实是提供输入的设备(键盘和鼠标也是),但它也属于 usb 子系统和其他几个子系统。这意味着你的摄像头被添加到了多个子系统,并且看起来像多个设备。如果你选择了错误的子系统,那么你的规则可能无法按你期望的那样工作,或者根本无法工作。

因此,第三件事就是检查网络摄像头被添加到的所有子系统,并选择正确的那个。为此,请再次拔下摄像头,然后运行:

ls /dev/video*

这将向你显示连接到本机的所有视频设备。如果你使用的是笔记本,大多数笔记本都带有内置摄像头,它可能会显示为 /dev/video0 。重新插入摄像头,然后再次运行 ls /dev/video*

现在,你应该看到多一个视频设备(可能是/dev/video1)。

现在,你可以通过运行 udevadm info -a /dev/video1 找出它所属的所有子系统:

udevadm info -a /dev/video1

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

 looking at device '/devices/pci0000:00/0000:00:1c.3/0000:04:00.0
  /usb3/3-1/3-1:1.0/video4linux/video1':
 KERNEL=="video1"
 SUBSYSTEM=="video4linux"
 DRIVER==""
 ATTR{dev_debug}=="0"
 ATTR{index}=="0"
 ATTR{name}=="HP Webcam HD 2300: HP Webcam HD"

[etc...]

输出持续了相当长的时间,但是你感兴趣的只是开头的部分:SUBSYSTEM =="video4linux"。你可以将这行文本直接复制粘贴到你的规则中。输出的其余部分(为简洁未显示)为你提供了更多的信息,例如制造商和型号 ID,同样是以你可以复制粘贴到你的规则中的格式。

现在,你有了识别设备的方式吗,并明确了什么事件应该触发该动作,该对设备进行修改了。

规则的下一部分,SYMLINK+="mywebcam", TAG+="systemd", MODE="0666" 告诉 Udev 做三件事:首先,你要创建设备的符号链接(例如 /dev/video1/dev/mywebcam。这是因为你无法预测系统默认情况下会把那个设备叫什么。当你拥有内置摄像头并热插拔一个新的时,内置摄像头通常为 /dev/video0,而外部摄像头通常为 /dev/video1。但是,如果你在插入外部 USB 摄像头的情况下重启计算机,则可能会相反,内部摄像头可能会变成 /dev/video1 ,而外部摄像头会变成 /dev/video0。这想告诉你的是,尽管你的图像捕获脚本(稍后将看到)总是需要指向外部摄像头设备,但是你不能依赖它是 /dev/video0/dev/video1。为了解决这个问题,你告诉 Udev 创建一个符号链接,该链接在设备被添加到 video4linux 子系统的那一刻起就不会再变,你将使你的脚本指向该链接。

第二件事就是将 systemd 添加到与此规则关联的 Udev 标记列表中。这告诉 Udev,该规则触发的动作将由 systemd 管理,即它将是某种 systemd 服务。

注意在这个两种情况下是如何使用 += 运算符的。这会将值添加到列表中,这意味着你可以向 SYMLINKTAG 添加多个值。

另一方面,MODE 值只能包含一个值(因此,你可以使用简单的 = 赋值运算符)。MODE 的作用是告诉 Udev 谁可以读或写该设备。如果你熟悉 chmod(你读到此文, 应该会熟悉),你就也会熟悉如何用数字表示权限。这就是它的含义:0666 的含义是 “向所有人授予对设备的读写权限”。

最后, ENV{SYSTEMD_WANTS}="webcam.service" 告诉 Udev 要运行什么 systemd 服务。

将此规则保存到 /etc/udev/rules.d 目录名为 90-webcam.rules(或类似的名称)的文件中,你可以通过重启机器或运行以下命令来加载它:

sudo udevadm control --reload-rules && udevadm trigger

最后的服务

Udev 规则触发的服务非常简单:

# webcam.service

[Service]
Type=simple
ExecStart=/home/[user name]/bin/checkimage.sh

基本上,它只是运行存储在你个人 bin/ 中的 checkimage.sh 脚本并将其放到后台。这是你在先前的文章中看过的内容。它看起来似乎很小,但那只是因为它是被 Udev 规则调用的,你刚刚创建了一种特殊的 systemd 单元,称为 device 单元。 恭喜。

至于 webcam.service 调用的 checkimage.sh 脚本,有几种方法从摄像头抓取图像并将其与前一个图像进行比较以检查变化(这是 checkimage.sh 所做的事),但这是我的方法:

#!/bin/bash 
# This is the checkimage.sh script

mplayer -vo png -frames 1 tv:// -tv driver=v4l2:width=640:height=480:device=
    /dev/mywebcam &>/dev/null 
mv 00000001.png /home/[user name]/monitor/monitor.png 

while true 
do 
   mplayer -vo png -frames 1 tv:// -tv driver=v4l2:width=640:height=480:device=/dev/mywebcam &>/dev/null 
   mv 00000001.png /home/[user name]/monitor/temp.png 

   imagediff=`compare -metric mae /home/[user name]/monitor/monitor.png /home/[user name]
       /monitor/temp.png /home/[user name]/monitor/diff.png 2>&1 > /dev/null | cut -f 1 -d " "` 
   if [ `echo "$imagediff > 700.0" | bc` -eq 1 ] 
       then 
           mv /home/[user name]/monitor/temp.png /home/[user name]/monitor/monitor.png 
       fi 
    
   sleep 0.5 
done

首先使用MPlayer从摄像头抓取一帧(00000001.png)。注意,我们怎样将 mplayer 指向 Udev 规则中创建的 mywebcam 符号链接,而不是指向 video0video1。然后,将图像传输到主目录中的 monitor/ 目录。然后执行一个无限循环,一次又一次地执行相同的操作,但还使用了Image Magick 的 compare 工具来查看最后捕获的图像与 monitor/ 目录中已有的图像之间是否存在差异。

如果图像不同,则表示摄像头的镜框里某些东西动了。该脚本将新图像覆盖原始图像,并继续比较以等待更多变动。

插线

所有东西准备好后,当你插入摄像头后,你的 Udev 规则将被触发并启动 webcam.servicewebcam.service 将在后台执行 checkimage.sh ,而 checkimage.sh 将开始每半秒拍一次照。你会感觉到,因为摄像头的 LED 在每次拍照时将开始闪。

与往常一样,如果出现问题,请运行:

systemctl status webcam.service

检查你的服务和脚本正在做什么。

接下来

你可能想知道:为什么要覆盖原始图像?当然,系统检测到任何动静,你都想知道发生了什么,对吗?你是对的,但是如你在下一部分中将看到的那样,将它们保持原样,并使用另一种类型的 systemd 单元处理图像将更好,更清晰和更简单。

请期待下一篇。


via: https://www.linux.com/blog/intro-to-linux/2018/6/systemd-services-reacting-change

作者:Paul Brown 选题:lujun9972 译者:messon007 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux shell 允许你将命令彼此链接在一起,一次触发执行复杂的操作,并且可以对此创建别名作为快捷方式。

让我们将继续我们的别名系列。到目前为止,你可能已经阅读了我们的关于别名的第一篇文章,并且应该非常清楚它们是如何为你省去很多麻烦的最简单方法。例如,你已经看到它们帮助我们减少了输入,让我们看看别名派上用场的其他几个案例。

别名即快捷方式

Linux shell 最美妙的事情之一是可以使用数以万计的选项和把命令连接在一起执行真正复杂的操作。好吧,也许这种美丽是在旁观者的眼中的,但是我们觉得这个功能很实用。

不利的一面是,你经常需要记得难以记忆或难以打字出来的命令组合。比如说硬盘上的空间非常宝贵,而你想要做一些清洁工作。你的第一步可能是寻找隐藏在你的家目录里的东西。你可以用来判断的一个标准是查找不再使用的内容。ls 可以帮助你:

ls -lct

上面的命令显示了每个文件和目录的详细信息(-l),并显示了每一项上次访问的时间(-c),然后它按从最近访问到最少访问的顺序排序这个列表(-t)。

这难以记住吗?你可能不会每天都使用 -c-t 选项,所以也许是吧。无论如何,定义一个别名,如:

alias lt='ls -lct'

会更容易一些。

然后,你也可能希望列表首先显示最旧的文件:

alias lo='lt -F | tac'

 title=

图 1:使用 lt 和 lo 别名。

这里有一些有趣的事情。首先,我们使用别名(lt)来创建另一个别名 —— 这是完全可以的。其次,我们将一个新参数传递给 lt(后者又通过 lt 别名的定义传递给了 ls)。

-F 选项会将特殊符号附加到项目的名称后,以便更好地区分常规文件(没有符号)和可执行文件(附加了 *)、目录文件(以 / 结尾),以及所有链接文件、符号链接文件(以 @ 符号结尾)等等。-F 选项是当你回归到单色终端的日子里,没有其他方法可以轻松看到列表项之间的差异时用的。在这里使用它是因为当你将输出从 lt 传递到 tac 时,你会丢失 ls 的颜色。

第三件我们需要注意的事情是我们使用了管道。管道用于你将一个命令的输出传递给另外一个命令时。第二个命令可以使用这些输出作为它的输入。在包括 Bash 在内的许多 shell 里,你可以使用管道符(|) 来做传递。

在这里,你将来自 lt -F 的输出导给 tactac 这个命令有点玩笑的意思,你或许听说过 cat 命令,它名义上用于将文件彼此连接(concat),而在实践中,它被用于将一个文件的内容打印到终端。tac 做的事情一样,但是它是以逆序将接收到的内容输出出来。明白了吗?cattac,技术人有时候也挺有趣的。

cattac 都能输出通过管道传递过来的内容,在这里,也就是一个按时间顺序排序的文件列表。

那么,在有些离题之后,最终我们得到的就是这个列表将当前目录中的文件和目录以新鲜度的逆序列出(即老的在前)。

最后你需要注意的是,当在当前目录或任何目录运行 lt 时:

# 这可以工作:
lt
# 这也可以:
lt /some/other/directory

……而 lo 只能在当前目录奏效:

# 这可工作:
lo
# 而这不行:
lo /some/other/directory

这是因为 Bash 会展开别名的组分。当你键入:

lt /some/other/directory

Bash 实际上运行的是:

ls -lct /some/other/directory

这是一个有效的 Bash 命令。

而当你键入:

lo /some/other/directory

Bash 试图运行:

ls -lct -F | tac /some/other/directory

这不是一个有效的命令,主要是因为 /some/other/directory 是个目录,而 cattac 不能用于目录。

更多的别名快捷方式

  • alias lll='ls -R' 会打印出目录的内容,并深入到子目录里面打印子目录的内容,以及子目录的子目录,等等。这是一个查看一个目录下所有内容的方式。
  • mkdir='mkdir -pv' 可以让你一次性创建目录下的目录。按照 mkdir 的基本形式,要创建一个包含子目录的目录,你必须这样:
mkdir newdir
mkdir newdir/subdir

或这样:

mkdir -p newdir/subdir

而用这个别名你将只需要这样就行:

mkdir newdir/subdir

你的新 mkdir 也会告诉你创建子目录时都做了什么。

别名也是一种保护

别名的另一个好处是它可以作为防止你意外地删除或覆写已有的文件的保护措施。你可能听说过这个 Linux 新用户的传言,当他们以 root 身份运行:

rm -rf /

整个系统就爆了。而决定输入如下命令的用户:

rm -rf /some/directory/ *

就很好地干掉了他们的家目录的全部内容。这里不小心键入的目录和 * 之间的那个空格有时候很容易就会被忽视掉。

这两种情况我们都可以通过 alias rm='rm -i' 别名来避免。-i 选项会使 rm 询问用户是否真的要做这个操作,在你对你的文件系统做出不可弥补的损失之前给你第二次机会。

对于 cp 也是一样,它能够覆盖一个文件而不会给你任何提示。创建一个类似 alias cp='cp -i' 来保持安全吧。

下一次

我们越来越深入到了脚本领域,下一次,我们将沿着这个方向,看看如何在命令行组合命令以给你真正的乐趣,并可靠地解决系统管理员每天面临的问题。


via: https://www.linux.com/blog/learn/2019/1/aliases-protect-and-serve

作者:Paul Brown 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在之前的该系列的部分中,你学习了有关目录访问目录的权限是如何工作的。你在这些文章中学习的大多数内容都可应用于文件,除了如何让一个文件变成可执行文件。

因此让我们在开始之前先解决这个问题。

不需要 .exe 扩展名

在其他操作系统中,一个文件的性质通常由它的后缀决定。如果一个文件有一个 .jpg 扩展,操作系统会认为它是一幅图像;如果它以 .wav 结尾,它是一个音频文件;如果它在文件名末尾以 .exe 结尾,它就是一个你可以执行的程序。

这导致了严重的问题,比如说木马可以伪装成文档文件。幸运的是,在 Linux 下事物不是这样运行的。可以确定的是,你可能会看到有些可执行文件是以 .sh 结尾暗示它们是可执行的脚本,但是这大部分是为了便于人眼找到文件,就像你使用 ls --color 将可执行文件的名字以亮绿色显示的方式相同。

事实上大多数应用根本没有扩展名。决定一个文件是否是一个真正程序的是 x (指可执行的)位。你可以通过运行以下命令使任何文件变得可执行,

chmod a+x some_program

而不管它的扩展名是什么或者是否存在。在上面命令中的 x 设置了 x 位,a 说明你为所有用户设置它。你同样可以为一组用户设置成拥有这个文件(g+x),或者只为一个用户——拥有者——设置 (u+x)。

尽管我们会在该系列之后的部分包含从命令行创建和运行脚本的内容,并学习通过输入它的路径并在结尾加上程序名的方式运行一个程序:

path/to/directory/some_program

或者,如果你当前在相同目录,你可以使用:

./some_program

还有其他方式可以使你的程序在目录树的任意位置运行 (提示:查询 $PATH 环境变量),但是当我们讨论 shell 脚本的时候你会读到这些。

复制、移动、链接

明显地,从命令行修改和处理文件有很多的方式,而不仅仅是处理它们的权限。当你试图打开一个不存在的文件是,大多数应用会创建一个新文件。如果 test.txt 当前并不存在,下列命令:

nano test.txt
vim test.txt

nanovim 是流行的命令行文本编辑器)都将为你创建一个空的 test.txt 文件来编辑。

你可以通过 “触摸” (touch)来创建一个空的文件,

touch test.txt

会创建一个文件,但是不会在任何应用中打开它。

你可以使用 cp 来拷贝一个文件到另一个位置,或者使用一个不同的名字:

cp test.txt copy_of_test.txt

你也可以拷贝一堆文件:

cp *.png /home/images

上面的命令拷贝当前目录下的所有 PNG 文件到相对你的主目录下的 images/ 目录。在你尝试之前 images/ 目录必须存在, 不然 cp 将显示一个错误。同样的,警惕,当你复制一个文件到一个已经包含相同名字的文件的目录时,cp 会静默地用新文件覆盖老的文件。

你可以使用:

cp -i *.png /home/images

如果你想要 cp 命令在有任何危险时警告你 (-i 选项代表交互式的)。

你同样可以复制整个目录,但是为了做到这样,你需要 -r 选项:

cp -rv directory_a/ directory_b

-r 选项代表递归,意味着 cp 会向下探索目录 directory_a,复制所有的文件和子目录下内部包含的。我个人喜欢包含 -v 选项,因为它使 cp 冗长而啰嗦,意味着它会显示你当前它正在做什么而不是仅仅静默的复制然后存在。

mv 命令移动东西。也就是说,它移动文件从一个位置到另一个位置。最简单的形式,mv 表现的更像 cp

mv test.txt new_test.txt

上面的命令使 new_test.txt 出现,test.txt 消失。

mv *.png /home/images

移动当前目录下所有的 PNG 文件到相对于你的主目录的 images/ 目录。同样的你必须小心你没有意外的覆盖已存在的文件。使用

mv -i *.png /home/images

如果你想站在安全的角度,你可以使用与 cp 相同的方式。

除了移动与拷贝的不同外,另一个 mvcp 之间的不同是当你移动目录时:

mv directory_a/ directory_b

不需要添加递归的标志。这是因为你实际做的是重命名一个目录,与第一个例子相同,你做的是重命名文件。实际上,即使你从一个目录到另一个目录 “移动” 一个文件,只要两个目录在相同的存储设备和分区,你就是在重命名文件。

你可以做一个实验来证明。 time 是一个工具来让你测量一个命令花费多久来执行。找一个非常大的文件,可以是几百 MB 甚至 几 GB (例如一个长视频),像下方这样尝试拷贝到另一个目录:

$ time cp hefty_file.mkv another_directory/
real 0m3,868s
user 0m0,016s
sys 0m0,887s

下面是 time 的输出。需要关注的是第一行, real 时间。它花费了几乎 4 秒来拷贝 355 MB 的 hefty_file.mkvanother_directory/ 目录。

现在让我们尝试移动它:

$ time mv hefty_file.mkv another_directory/
real 0m0,004s
user 0m0,000s
sys 0m0,003s

移动几乎是瞬时的!这是违反直觉的,因为看起来 mv 必须复制这个文件然后删除原来的。这是 mv 对比 cp 命令必须做的两件事。但是,实际上,mv 快了 1000 倍。

这是因为文件系统结构中,它的所有目录树,只为了让用户便利而存在。在每个分区的开始,有一个称作分区表的东西告诉操作系统在实际的物理磁盘上去哪找每个文件。在磁盘上,数据没有分为目录甚至是文件。作为替代的是轨道、扇区和簇。当你在相同分区 “移动” 一个文件时,操作系统实际做的仅仅是在分区表中改变了那个文件的入口,但它仍然指向磁盘上相同的簇信息。

是的!移动是一个谎言!至少在相同分区下是。如果你试图移动一个文件到一个不同的分区或者不同的设备, mv 仍然很快,但可以察觉到它比在相同分区下移动文件慢了。这是因为实际上发生了复制和清除数据。

重命名

有几个不同的命令行 rename 工具。没有一个像 cpmv 那样固定,并且它们工作的方式都有一点不同,相同的一点是它们都被用来改变文件名的部分。

在 Debian 和 Ubuntu 中, 默认的 rename 工具使用 正则表达式(字符组成的字符串模式)来大量的改变目录中的文件。命令:

rename 's/\.JPEG$/.jpg/' *

将改变所有扩展名为 JPEG 的文件为 jpg。文件 IMG001.JPEG 变成 IMG001.jpgmy_pic.JPEG 变成 my_pic.jpg,等等。

另一个 rename 版本默认在 Manjaro 上可获得,这是一个 Arch 的衍生版,更简单,但是可能没有那么强大:

rename .JPEG .jpg *

这和你之前看到的上面做相同的重命名操作。在这个版本,.JPEG 是你想改变的字符组成的字符串,.jpg 是你想要改变成为的,* 表示当前目录下的所有文件。

基本原则是如果你所做的仅仅是重命名一个文件或者目录,你最好用 mv,这是因为 mv 在所有分发版上都是可靠一致的。

了解更多

查看 mvcp 的 man 页面了解更多。运行

man cp

或者 man mv

来阅读这些命令自带的所有选项,这些使他们使用起来更强大和安全。


via: https://www.linux.com/blog/2018/8/linux-beginners-moving-things-around

作者:Paul Brown 选题:lujun9972 译者:warmfrog 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

 title=

我们继续来看方括号的用法,它们甚至还可以在 Bash 当中作为一个命令使用。

欢迎回到我们的方括号专题。在前一篇文章当中,我们介绍了方括号在命令行中可以用于通配操作,如果你已经读过前一篇文章,就可以从这里继续了。

方括号还可以以一个命令的形式使用,就像这样:

[ "a" = "a" ]

上面这种 [ ... ] 的形式就可以看成是一个可执行的命令。要注意,方括号内部的内容 "a" = "a" 和方括号 [] 之间是有空格隔开的。因为这里的方括号被视作一个命令,因此要用空格将命令和它的参数隔开。

上面这个命令的含义是“判断字符串 "a" 和字符串 "a" 是否相同”,如果判断结果为真,那么 [ ... ] 就会以 状态码 status code 0 退出,否则以状态码 1 退出。在之前的文章中,我们也有介绍过状态码的概念,可以通过 $? 变量获取到最近一个命令的状态码。

分别执行

[ "a" = "a" ]
echo $?

以及

[ "a" = "b" ]
echo $?

这两段命令中,前者会输出 0(判断结果为真),后者则会输出 1(判断结果为假)。在 Bash 当中,如果一个命令的状态码是 0,表示这个命令正常执行完成并退出,而且其中没有出现错误,对应布尔值 true;如果在命令执行过程中出现错误,就会返回一个非零的状态码,对应布尔值 false。而 [ ... ] 也同样遵循这样的规则。

因此,[ ... ] 很适合在 if ... thenwhileuntil 这种在代码块结束前需要判断是否达到某个条件结构中使用。

对应使用的逻辑判断运算符也相当直观:

[ STRING1 = STRING2 ] => 检查字符串是否相等
[ STRING1 != STRING2 ] => 检查字符串是否不相等
[ INTEGER1 -eq INTEGER2 ] => 检查整数 INTEGER1 是否等于 INTEGER2 
[ INTEGER1 -ge INTEGER2 ] => 检查整数 INTEGER1 是否大于等于 INTEGER2
[ INTEGER1 -gt INTEGER2 ] => 检查整数 INTEGER1 是否大于 INTEGER2
[ INTEGER1 -le INTEGER2 ] => 检查整数 INTEGER1 是否小于等于 INTEGER2
[ INTEGER1 -lt INTEGER2 ] => 检查整数 INTEGER1 是否小于 INTEGER2
[ INTEGER1 -ne INTEGER2 ] => 检查整数 INTEGER1 是否不等于 INTEGER2
等等……

方括号的这种用法也可以很有 shell 风格,例如通过带上 -f 参数可以判断某个文件是否存在:

for i in {000..099}; \
 do \
  if [ -f file$i ]; \
  then \
   echo file$i exists; \
  else \
   touch file$i; \
   echo I made file$i; \
  fi; \
done

如果你在上一篇文章使用到的测试目录中运行以上这串命令,其中的第 3 行会判断那几十个文件当中的某个文件是否存在。如果文件存在,会输出一条提示信息;如果文件不存在,就会把对应的文件创建出来。最终,这个目录中会完整存在从 file000file099 这一百个文件。

上面这段命令还可以写得更加简洁:

for i in {000..099};\
do\
 if [ ! -f file$i ];\
 then\
  touch file$i;\
  echo I made file$i;\
 fi;\
done

其中 ! 运算符表示将判断结果取反,因此第 3 行的含义就是“如果文件 file$i 不存在”。

可以尝试一下将测试目录中那几十个文件随意删除几个,然后运行上面的命令,你就可以看到它是如何把被删除的文件重新创建出来的。

除了 -f 之外,还有很多有用的参数。-d 参数可以判断某个目录是否存在,-h 参数可以判断某个文件是不是一个符号链接。可以用 -G 参数判断某个文件是否属于某个用户组,用 -ot 参数判断某个文件的最后更新时间是否早于另一个文件,甚至还可以判断某个文件是否为空文件。

运行下面的几条命令,可以向几个文件中写入一些内容:

echo "Hello World" >> file023
echo "This is a message" >> file065
echo "To humanity" >> file010

然后运行:

for i in {000..099};\
do\
 if [ ! -s file$i ];\
 then\
  rm file$i;\
  echo I removed file$i;\
 fi;\
done

你就会发现所有空文件都被删除了,只剩下少数几个非空的文件。

如果你还想了解更多别的参数,可以执行 man test 来查看 test 命令的 man 手册(test[ ... ] 的命令别名)。

有时候你还会看到 [[ ... ]] 这种双方括号的形式,使用起来和单方括号差别不大。但双方括号支持的比较运算符更加丰富:例如可以使用 == 来判断某个字符串是否符合某个 模式 pattern ,也可以使用 <> 来判断两个字符串的出现顺序。

可以在 Bash 表达式文档中了解到双方括号支持的更多运算符。

下一集

在下一篇文章中,我们会开始介绍圆括号 () 在 Linux 命令行中的用法,敬请关注!

更多


via: https://www.linux.com/blog/learn/2019/4/using-square-brackets-bash-part-2

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

 title=

这篇文章将要介绍方括号及其在命令行中的不同用法。

看完花括号在命令行中的用法之后,现在我们继续来看方括号([])在上下文中是如何发挥作用的。

通配

方括号最简单的用法就是通配。你可能在知道“ Globbing ”这个概念之前就已经通过通配来匹配内容了,列出具有相同特征的多个文件就是一个很常见的场景,例如列出所有 JPEG 文件:

ls *.jpg

使用 通配符 wildcard 来得到符合某个模式的所有内容,这个过程就叫通配。

在上面的例子当中,星号(*)就代表“0 个或多个字符”。除此以外,还有代表“有且仅有一个字符”的问号(?)。因此

ls d*k*

可以列出 darklyducky,而且 darkduck 也是可以被列出的,因为 * 可以匹配 0 个字符。而

ls d*k?

则只能列出 ducky,不会列出 darklydarkduck

方括号也可以用于通配。为了便于演示,可以创建一个用于测试的目录,并在这个目录下创建文件:

touch file0{0..9}{0..9}

(如果你还不清楚上面这个命令的原理,可以看一下另一篇介绍花括号的文章

执行上面这个命令之后,就会创建 file000file001、……、file099 这 100 个文件。

如果要列出这些文件当中第二位数字是 7 或 8 的文件,可以执行:

ls file0[78]?

如果要列出 file022file027file028file052file057file058file092file097file098,可以执行:

ls file0[259][278]

当然,不仅仅是 ls,很多其它的命令行工具都可以使用方括号来进行通配操作。但在删除文件、移动文件、复制文件的过程中使用通配,你需要有一点横向思维。

例如将 file010file029 这 30 个文件复制成 archive010archive029 这 30 个副本,不可以这样执行:

cp file0[12]? archive0[12]?

因为通配只能针对已有的文件,而 archive 开头的文件并不存在,不能进行通配。

而这条命令

cp file0[12]? archive0[1..2][0..9]

也同样不行,因为 cp 并不允许将多个文件复制到多个文件。在复制多个文件的情况下,只能将多个文件复制到一个指定的目录下:

mkdir archive
cp file0[12]? archive

这条命令是可以正常运行的,但它只会把这 30 个文件以同样的名称复制到 archive/ 目录下,而这并不是我们想要的效果。

如果你阅读过我关于花括号的文章,你大概会记得可以使用 % 来截掉字符串的末尾部分,而使用 # 则可以截掉字符串的开头部分。

例如:

myvar="Hello World"
echo Goodbye Cruel ${myvar#Hello}

就会输出 Goodbye Cruel World,因为 #Hellomyvar 变量中开头的 Hello 去掉了。

在通配的过程中,也可以使用这一个技巧。

for i in file0[12]?;\
do\
cp $i archive${i#file};\
done

上面的第一行命令告诉 Bash 需要对所有 file01 开头或者 file02 开头,且后面只跟一个任意字符的文件进行操作,第二行的 do 和第四行的 done 代表需要对这些文件都执行这一块中的命令。

第三行就是实际的复制操作了,这里使用了两次 $i 变量:第一次在 cp 命令中直接作为源文件的文件名使用,第二次则是截掉文件名开头的 file 部分,然后在开头补上一个 archive,也就是这样:

"archive" + "file019" - "file" = "archive019"

最终整个 cp 命令展开为:

cp file019 archive019

最后,顺带说明一下反斜杠 \ 的作用是将一条长命令拆分成多行,这样可以方便阅读。

在下一节,我们会了解方括号的更多用法,敬请关注。


via: https://www.linux.com/blog/2019/3/using-square-brackets-bash-part-1

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

让我们继续我们的 Bash 基础之旅,来近距离观察一下花括号,了解一下如何和何时使用它们。

在前面的 Bash 基础系列文章中,我们或多或少地使用了一些还没有讲到的符号。在之前文章的很多例子中,我们都使用到了括号,但并没有重点讲解关于括号的内容。

这个系列接下来的文章中,我们会研究括号们的用法:如何使用这些括号?将它们放在不同的位置会有什么不同的效果?除了圆括号、方括号、花括号以外,我们还会接触另外的将一些内容“包裹”起来的符号,例如单引号、双引号和反引号。

在这周,我们先来看看花括号 {}

构造序列

花括号在之前的《点的含义》这篇文章中已经出现过了,当时我们只对点号 . 的用法作了介绍。但在构建一个序列的过程中,同样不可以缺少花括号。

我们使用

echo {0..10}

来顺序输出 0 到 10 这 11 个数。使用

echo {10..0}

可以将这 11 个数倒序输出。更进一步,可以使用

echo {10..0..2}

来跳过其中的奇数。

echo {z..a..2}

则从倒序输出字母表,并跳过其中的第奇数个字母。

以此类推。

还可以将两个序列进行组合:

echo {a..z}{a..z}

这个命令会将从 aa 到 zz 的所有双字母组合依次输出。

这是很有用的。在 Bash 中,定义一个数组的方法是在圆括号 () 中放置各个元素并使用空格隔开,就像这样:

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

如果需要获取数组中的元素,就要使用方括号 [] 并在其中填入元素的索引:

$ echo ${month[3]} # 数组索引从 0 开始,因此 [3] 对应第 4 个元素
Apr

先不要过分关注这里用到的三种括号,我们等下会讲到。

注意,像上面这样,我们可以定义这样一个数组:

letter_combos=({a..z}{a..z})

其中 letter_combos 变量指向的数组依次包含了从 aa 到 zz 的所有双字母组合。

因此,还可以这样定义一个数组:

dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})

在这里,dec2bin 变量指向的数组按照升序依次包含了所有 8 位的二进制数,也就是 00000000、00000001、00000010,……,11111111。这个数组可以作为一个十进制数到 8 位二进制数的转换器。例如将十进制数 25 转换为二进制数,可以这样执行:

$ echo ${dec2bin[25]}
00011001

对于进制转换,确实还有更好的方法,但这不失为一个有趣的方法。

参数展开

再看回前面的

echo ${month[3]}

在这里,花括号的作用就不是构造序列了,而是用于 参数展开 parameter expansion 。顾名思义,参数展开就是将花括号中的变量展开为这个变量实际的内容。

我们继续使用上面的 month 数组来举例:

month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")

注意,Bash 中的数组索引从 0 开始,因此 3 代表第 4 个元素 "Apr"。因此 echo ${month[3]} 在经过参数展开之后,相当于 echo "Apr"

像上面这样将一个数组展开成它所有的元素,只是参数展开的其中一种用法。另外,还可以通过参数展开的方式读取一个字符串变量,并对其进行处理。

例如对于以下这个变量:

a="Too longgg"

如果执行:

echo ${a%gg}

可以输出 “too long”,也就是去掉了最后的两个 g。

在这里,

  • ${...} 告诉 shell 展开花括号里的内容
  • a 就是需要操作的变量
  • % 告诉 shell 需要在展开字符串之后从字符串的末尾去掉某些内容
  • gg 是被去掉的内容

这个特性在转换文件格式的时候会比较有用,我来举个例子:

ImageMagick 是一套可以用于操作图像文件的命令行工具,它有一个 convert 命令。这个 convert 命令的作用是可以为某个格式的图像文件制作一个另一格式的副本。

下面这个命令就是使用 convert 为 JPEG 格式图像 image.jpg 制作一个 PNG 格式的图像副本 image.png

convert image.jpg image.png

在很多 Linux 发行版中都预装了 ImageMagick,如果没有预装,一般可以在发行版对应的软件管理器中找到。

继续来看,在对变量进行展开之后,就可以批量执行相类似的操作了:

i=image.jpg
convert $i ${i%jpg}png

这实际上是将变量 i 末尾的 "jpg" 去掉,然后加上 "png",最终将整个命令拼接成 convert image.jpg image.png

如果你觉得并不怎么样,可以想象一下有成百上千个图像文件需要进行这个操作,而仅仅运行:

for i in *.jpg; do convert $i ${i%jpg}png; done

就瞬间完成任务了。

如果需要去掉字符串开头的部分,就要将上面的 % 改成 # 了:

$ a="Hello World!"
$ echo Goodbye${a#Hello}
Goodbye World!

参数展开还有很多用法,但一般在写脚本的时候才会需要用到。在这个系列以后的文章中就继续提到。

合并输出

最后介绍一个花括号的用法,这个用法很简单,就是可以将多个命令的输出合并在一起。首先看下面这个命令:

echo "I found all these PNGs:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > PNGs.txt

以分号分隔开的几条命令都会执行,但只有最后的 ls 命令的结果输出会被重定向到 PNGs.txt 文件中。如果将这几条命令用花括号包裹起来,就像这样:

{ echo "I found all these PNGs:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > PNGs.txt

执行完毕后,可以看到 PNGs.txt 文件中会包含两次 echo 的内容、find 命令查找到的 PNG 文件以及最后的 ls 命令结果。

需要注意的是,花括号与命令之间需要有空格隔开。因为这里的花括号 {} 是作为 shell 中的保留字,shell 会将这两个符号之间的输出内容组合到一起。

另外,各个命令之间要用分号 ; 分隔,否则命令无法正常运行。

下期预告

在后续的文章中,我会介绍其它“包裹”类符号的用法,敬请关注。


via: https://www.linux.com/blog/learn/2019/2/all-about-curly-braces-bash

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出