Linux 脚本编程终极指南(八)

原文:annas-archive.org/md5/ba00d8e91eed140a0762b25e806ea7fe

译者:飞龙

协议:CC BY-NC-SA 4.0

第二十二章:介绍 Z Shell 脚本编写

到目前为止,我们主要讨论了 bash,因为它是 Linux 的主要登录 shell 和脚本环境。近年来,一个相对较新的登录 shell Z Shell (zsh) 作为默认登录 shell 越来越受欢迎。但即使在预装 zsh 作为登录 shell 的操作系统上,bash 仍然存在,并且大多数人仍然将其用于脚本编写。然而,需要注意的是,在 zsh 中进行脚本编写确实有一些优势,比如更好的数学能力、增强的变量展开功能,以及能够使用本地 zsh 命令而不是像 findgrepsed 这样的实用程序。这些特性可以帮助您创建比等效的 bash 脚本更简单、更轻量的脚本。

大多数关于 zsh 的文章和 YouTube 视频都集中在用户界面增强上,而很少关注 zsh 脚本编写。不幸的是,少数关注 zsh 脚本编写的书籍和教程写得不太好,您不会从中获得太多收获。希望我能提供一些澄清。

与其提供详细的教程,我更想简要概述 zsh 脚本编写,让您自己决定是否适合您。

本章的主题包括:

  • 介绍 zsh

  • 安装 zsh

  • 理解 zsh 脚本编写的独特功能

  • 使用 zsh 模块

如果您准备好了,让我们开始吧。

技术要求

您可以在任何 Linux 虚拟机上使用本章内容。与往常一样,您可以通过执行以下操作获取演示脚本:

git clone https://github.com/PacktPublishing/The-Ultimate-Linux-Shell-Scripting-Guide.git 

介绍 zsh

Z Shell 由 Paul Falstad 于 1990 年创建。它是 Bourne Shell 的扩展,同时还包括来自 bash、Korn Shell (ksh) 和 C Shell (tcsh) 的功能。

C Shell 曾在 Unix 和类 Unix 发行版中颇受欢迎,但它与你之前接触的任何东西大不相同。编写 C Shell 脚本更类似于编写 C 语言程序,而不是你所熟悉的方式。所以如果你是 C 语言程序员,你可能会喜欢它。如果不是,那你可能不会那么喜欢。

Z Shell 是 macOS 和 Kali Linux 的默认登录 shell。对于几乎所有其他系统,您都需要自行安装。至于 zsh 脚本编写,大多数在 bash 中学到的内容也适用于 zsh。因此,在本章中,我将仅介绍 zsh 的独特功能,并展示一些简单的脚本示例。

安装 zsh

在我尝试过的所有 Linux 和 BSD 类型发行版以及 OpenIndiana 上,都可以使用 zsh 包。在所有情况下,包名称都是 zsh,因此您可以使用正常的包管理器安装它。

在 BSD 系统中,你会遇到与bash相同的路径问题。也就是说,在 BSD 系统中,zsh的可执行文件位于/usr/local/bin/目录下,而不是/bin/目录下。不过没关系,你可以像在bash中那样,在/bin/目录下创建一个符号链接。在我的 FreeBSD 机器上,命令如下:

donnie@freebsd-zfs:~ $ which zsh
/usr/local/bin/zsh
donnie@freebsd-zfs:~ $ sudo ln -s /usr/local/bin/zsh /bin/zsh
donnie@freebsd-zfs:~ $ which zsh
/bin/zsh
donnie@freebsd-zfs:~ $ 

现在,如果你需要在 Linux、OpenIndiana 和 BSD 机器上运行你的zsh脚本,可以使用相同的 shebang 行,格式如下:

#!/bin/zsh 

如果你想将zsh作为临时登录 shell 使用,可以直接在当前 shell 的命令提示符下输入zsh。如果你想将zsh设置为永久登录 shell,则可以在 Linux 和 BSD 系统上使用chsh命令,在 OpenIndiana 上使用passwd命令。Linux 和 BSD 系统上可以这样设置:

donnie@ubuntu2404:~$ chsh -s /bin/zsh
Password:
donnie@ubuntu2404:~$ 

在 OpenIndiana 上的表现如下:

donnie@openindiana:~$ passwd -e
Enter existing login password:
Old shell: /bin/bash
New shell: /bin/zsh
passwd: password information changed for donnie
donnie@openindiana:~$ 

请注意,当你为自己的用户账户更改 shell 时,系统会提示你输入自己的用户密码。然而,这与sudo无关,因为即使是非特权用户,也可以在 Linux/BSD 系统上使用chsh,在 OpenIndiana 上使用passwd -e来更改自己的默认 shell。

第一次使用zsh登录时,你会看到一个设置菜单,内容如下所示:

This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~).  This function can help you with a few settings that should
make your use of the shell easier.
You can:
(q)  Quit and do nothing.  The function will be run again next time.
(0)  Exit, creating the file ~/.zshrc containing just a comment.
     That will prevent this function being run again.
(1)  Continue to the main menu.
--- Type one of the keys in parentheses --- 

按下1键返回主菜单,并选择你首选的设置选项。配置将保存到你家目录下的.zshrc文件中。

接下来,让我们来看一下zsh脚本的一些独特功能。

理解zsh脚本的独特功能

zsh中编写脚本时,你可以利用一些bash没有的增强功能。这里简要回顾一下其中的一些增强功能。

变量扩展的区别

第八章—基本 Shell 脚本构建中,我解释了变量扩展的概念,这个概念有时也叫做参数扩展。这使得你可以编写一些很酷的脚本来完成很酷的事情,比如一次性修改一批文件的文件扩展名。bash中大部分的变量扩展构造在zsh中也能使用,但zsh还提供了一些额外的扩展功能,增加了更多的能力。让我们来看几个例子。

替换值

zsh中替换值的方式与bash相同,唯一的例外是,如果你在包含感叹号的文本字符串中替换值,在zsh脚本中需要对感叹号进行转义。例如,在bash中,效果如下:

donnie@fedora:~$ unset name
donnie@fedora:~$ echo "Hello, ${name:-Guest}!"
Hello, Guest!
donnie@fedora:~$ name=Horatio
donnie@fedora:~$ echo "Hello, ${name:-Guest}!"
Hello, Horatio!
donnie@fedora:~$ 

正如我在第八章中所解释的,variable_name:-default_value结构为任何尚未赋值的变量提供了默认值。这个结构本应在zsh中也能工作,但看我尝试它时发生了什么:

ubuntu2404% unset name
ubuntu2404% echo "Hello, ${name:-Guest}!"
dquote> "
Hello, Guest
ubuntu2404% 

当我在输入完echo命令后按下Enter键,zsh会将我带到dquote>提示符。如果我第二次按下"键,就能得到我想要的输出。为了第一次就正确输出,我只需在!之前加上\,像这样:

ubuntu2404% unset name               
ubuntu2404% echo "Hello, ${name:-Guest}\!"
Hello, Guest!
ubuntu2404% name=Horatio
ubuntu2404% echo "Hello, ${name:-Guest}\!"
Hello, Horatio!
Ubuntu2404% 

所以,在感叹号前加上\,它就能正常工作。我不知道为什么zsh中感叹号需要转义,不过,反正能用就行,对吧?

子字符串替换

子字符串替换在bash中部分有效,但不是完全有效。我的意思是这样。

我将首先创建name变量,赋值为lionel。然后,在bashzsh中,我会尝试将第一个小写字母l替换成大写字母L。首先在bash中,像这样:

donnie@fedora:~$ echo $SHELL
/bin/bash
donnie@fedora:~$ name="lionel"
donnie@fedora:~$ echo "${name/l/L}"
Lionel
donnie@fedora:~$ 

现在,在zsh中:

ubuntu2404% echo $SHELL
/bin/zsh
ubuntu2404% name="lionel"
ubuntu2404% echo "${name/l/L}"
Lionel
ubuntu2404% 

${name/l/L}构造中,你可以看到我首先列出了name变量,并用正斜杠后跟要替换的字母,然后是另一个正斜杠和要替换成的字母。如你所见,这在bashzsh中都能正常工作。我还可以通过在name变量后使用两个正斜杠来替换所有的小写l,像这样:

bash中:

donnie@fedora:~$ echo "${name//l/L}"
LioneL
donnie@fedora:~$ 

zsh中:

ubuntu2404% echo "${name//l/L}"               
LioneL
ubuntu2404% 

这个方法在bashzsh中都能使用。那么,如果它在这两种 shell 中都能工作,为什么我还要向你展示呢?其实,原因是:在bash中,这个方法仅在字符串不包含空格的情况下有效。

这里是我想表达的意思:

bash中:

donnie@fedora:~$ names="Donnie and Vicky and Cleopatra"
donnie@fedora:~$ echo "${names//and/&}"
Donnie and Vicky and Cleopatra
donnie@fedora:~$ 

这一次,我想把所有的and替换成&符号。但在bash中这行不通,因为字符串包含空格。我们来试试在zsh中做这个操作。

zsh中:

ubuntu2404% names="Donnie and Vicky and Cleopatra"
ubuntu2404% echo "${names//and/&}"
Donnie & Vicky & Cleopatra
ubuntu2404% 

zsh中,字符串中包含空格并不影响操作。

大小写转换

这里是完全相反情况的示例。这一次,我将展示一个在bash中有效,但在zsh中无效的例子。然后,我会展示在zsh中使用的替代方法。

首先,假设我们想要将分配给name变量的值的首字母大写。以下是在bash中的方法:

donnie@fedora:~$ name=horatio
donnie@fedora:~$ echo "${name^}"
Horatio
donnie@fedora:~$ 

我这里做的只是将一个^放在name变量的echo语句后面。现在,假设我想让所有字母大写,我可以使用两个^字符,像这样:

donnie@fedora:~$ echo "${name^^}"
HORATIO
donnie@fedora:~$ 

如果你有一个全大写的字符串,可以使用一个或两个逗号,将第一个字母或所有字母转换为小写,就像这样:

donnie@fedora:~$ name=HORATIO
donnie@fedora:~$ echo "${name,}"
hORATIO
donnie@fedora:~$ echo "${name,,}"
horatio
donnie@fedora:~$ 

zsh中,完全不管用,正如你看到的:

ubuntu2404% name=horatio
ubuntu2404% echo "${name^}"
zsh: bad substitution
ubuntu2404% echo "${name^^}"
zsh: bad substitution
ubuntu2404% name=HORATIO 
ubuntu2404% echo "${name,}"
zsh: bad substitution
ubuntu2404% echo "${name,,}"
zsh: bad substitution
ubuntu2404% 

我之所以向你展示这个,是因为一些zsh脚本书说这种方法在zsh中有效。但如你所见,显然无效。

那么,在zsh中的替代方法是什么呢?其实,你只需使用一个内建的zsh函数。我们首先使用U函数将整个字符串转换为大写:

ubuntu2404% name=horatio
ubuntu2404% echo "${(U)name}"
HORATIO
ubuntu2404% 

我没有在变量名后面加一对^字符,而是直接在name变量前加了一个(U)

据我所知,zsh中并没有直接转换第一个字母的函数。所以,我们只能像这样转换第一个字母:

ubuntu2404% name=horatio
ubuntu2404% capitalized_name=${name/h/H}
ubuntu2404% echo $capitalized_name
Horatio
ubuntu2404% 

这和我在上一部分所展示的方法是一样的。

接下来,我们来做一些通配符操作。

扩展文件通配符

文件模式匹配”,你问什么?嗯,其实你在本书的整个过程中都在使用它。只是我从未告诉过你它叫什么。它的意思是,你可以使用通配符字符,比如*?,同时处理多个文件。例如,你可以这样做:

donnie@fedora:~$ ls -l *.zip
-rw-r--r--. 1 root   root      32864546 Jul 27  2023 15827_zip.zip
-rw-r--r--. 1 root   root      49115374 Jul 27  2023 21261.zip
-rw-r--r--. 1 root   root      36996449 Jul 27  2023 46523.zip
. . .
. . .
-rw-r--r--. 1 root   root      21798397 Jul 27  2023 tmvx.zip
-rw-r--r--. 1 root   root         60822 Jul 27  2023 U_CAN_Ubuntu_20-04_LTS_V1R4_STIG_SCAP_1-2_Benchmark.zip
-rw-r--r--. 1 root   root        425884 Jul 27  2023 zoneinfo.zip
donnie@fedora:~$ 

我在这里做的只是使用*通配符来查看我目录中的所有.zip文件。

你可以在zsh中做所有这些操作,甚至更多。唯一的条件是你需要开启extendedglob功能。要验证它是否开启,检查你家目录下的.zshrc文件。你应该能看到像这样的行:

setopt autocd beep extendedglob nomatch notify 

如果extendedglob选项开启,那么你就可以使用它了。如果没有开启,只需添加它。那么,现在我们来看看几个扩展文件模式匹配的例子。

为什么zsh中的扩展文件模式匹配有用?嗯,实际上,在许多情况下,你可以使用zsh的扩展模式匹配功能,而不是使用find工具。你可能会觉得这比使用find更简单。或者,如果你习惯使用find,你可能更喜欢使用它。我会让你自己做决定。

此外,为了演示的目的,我主要用ls工具来展示这个功能。但你也可以使用文件模式匹配与其他工具一起使用,比如cpmvrmchmodchown

过滤ls输出

启用这个酷炫的功能后,你可以做一些很酷的事情,比如排除你不想看到的文件。例如,假设你想查看目录中的所有文件,但不包括.sh文件。只需这样做:

ubuntu2404% ls -d ^*.sh          
bashlib-0.4		    My_Files
demo_files		    supersecret
deshc-deb		   	    supersecret_trace.txt
Documents		           sysinfo.html
encrypted_password	    sysinfo_posix.lib
encrypted_password.sh.x.c  test.sh.x
expand_1.txt		    user_activity_for_donnie_2024-05-12_09-30.txt
expand_2.txt		    user_activity_for_donnie_2024-07-22_10-40.txt
expand.txt		    user_activity_for_horatio_2024-07-06_07-12.txt
FallOfSudo		    while_demo
file2.txt		           while_demo.sh.x.c
file3.jpg
ubuntu2404% 

*.sh前面的^是一个否定符号。这会阻止ls命令显示.sh文件。

注意,我必须为ls添加-d选项。否则,你还会看到任何可能存在的子目录中的文件。由于某种原因,^可以过滤掉我顶层目录中不需要的文件,但它不会过滤掉子目录中的不需要的文件。所以,如果没有-d,你还会看到子目录中的所有文件,包括那些你想过滤掉的文件。

你也可以使用*~操作符来实现相同的效果。看起来是这样的:

ubuntu2404% ls -d *~*.sh
bashlib-0.4		   My_Files
demo_files		   supersecret
deshc-deb		          supersecret_trace.txt
Documents		           sysinfo.html
encrypted_password	    sysinfo_posix.lib
encrypted_password.sh.x.c  test.sh.x
expand_1.txt		    user_activity_for_donnie_2024-05-12_09-30.txt
expand_2.txt		    user_activity_for_donnie_2024-07-22_10-40.txt
expand.txt		    user_activity_for_horatio_2024-07-06_07-12.txt
FallOfSudo		    while_demo
file2.txt		           while_demo.sh.x.c
file3.jpg
ubuntu2404% 

如你所见,输出与ls -d ^.sh的输出完全相同。要排除多种类型的文件,只需使用操作符(|),像这样:

ubuntu2404% ls -d *~(*.sh|*.jpg|*.txt|*.c)
bashlib-0.4  Documents		 My_Files      sysinfo_posix.lib
demo_files   encrypted_password  supersecret   test.sh.x
deshc-deb    FallOfSudo		 sysinfo.html  while_demo
ubuntu2404% 

所以在这个例子中,我过滤掉了.sh.jpg.txt.c文件。现在,让我们看看下一个技巧。

分组ls搜索

你在bash中做不到的另一件事是将ls搜索结果进行分组,像这样:

ubuntu2404% ls -ld (ex|test)*
-rw-r--r-- 1 donnie donnie    52 Apr 29 20:02 expand_1.txt
-rw-r--r-- 1 donnie donnie   111 Apr 29 20:02 expand_2.txt
-rw-r--r-- 1 donnie donnie    51 Apr 29 20:02 expand.txt
-rwxrw-r-- 1 donnie donnie    48 Jun 30 20:26 test.sh
-rw-rw-r-- 1 donnie donnie     0 Jul  5 19:31 test.sh.dec.sh
-rwxr-xr-x 1 donnie donnie 11440 Jul  5 19:12 test.sh.x
ubuntu2404% 

在这里,我使用|符号作为操作符,查看所有文件,其文件名的前部分是extest

使用文件模式匹配标志

正如你所知道的,Linux 和 Unix 操作系统通常区分大小写。因此,如果你使用 ls *.jpg 命令来查看所有 JPEG 类型的图像文件,你将完全错过任何可能有 *.JPG 扩展名的文件。使用 zsh 时,你可以使用通配符标志来使命令不区分大小写。下面是一个例子:

ubuntu2404% touch graphic1.jpg graphic2.JPG graphic3.jpg file3.jpg
ubuntu2404% ls *.jpg
file3.jpg  graphic1.jpg  graphic3.jpg
ubuntu2404% ls (#i)*.jpg
file3.jpg  graphic1.jpg  graphic2.JPG  graphic3.jpg
ubuntu2404% 

第二个 ls 命令中的 (#i) 就是通配符标志。# 表示这是一个标志,它会出现在你想要匹配的模式前面。

(#l) 标志会匹配小写或大写的模式,如果你为搜索指定小写模式,它就会匹配小写模式。如果你指定大写模式,那么它只会匹配大写模式。下面是它的样子:

ubuntu2404% ls (#l)*.jpg
file3.jpg  graphic1.jpg  graphic2.JPG  graphic3.jpg
ubuntu2404% ls (#l)*.JPG
graphic2.JPG
ubuntu2404% 

你可以看到,当我指定 *.jpg 作为模式时,它也找到了 .JPG 文件。但是,当我指定 .JPG 作为模式时,它只找到了 .JPG 文件。

现在,让我们扩展一些目录。

扩展目录

你可以使用通配符限定符查看不同类型的目录或文件。我们将从目录的限定符开始,具体包括:

  • *(F):这个扩展了非空目录。

  • *(^F):这个扩展了普通文件和空目录。

  • *(/^F):这个仅扩展空目录。

首先,让我们查看我主目录中的非空目录。

ubuntu2404% ls *(F)            
bashlib-0.4:
bashlib     config.cache  config.status  COPYING  Makefile     README
bashlib.in  config.log	  configure	 INSTALL  Makefile.in
. . .
. . .
My_Files:
afile.txt			somefile.txt	 yetanotherfile.txtx
anotherfile.txt			somepicture.jpg  yetanotheryetanotherfile.txt
haveicreatedenoughfilesyet.txt	somescript.sh
ubuntu2404% 

你可以看到,这个命令显示了非空目录及其内容。但它没有显示我顶层主目录中的任何文件。所以,一切正常。

现在,我想查看我顶层主目录中的所有文件,以及可能存在的任何空目录。我会这样做:

ubuntu2404% ls *(^F)
acl_demo.sh		   rsync_password.sh
diskspace.sh		   shutdown-update.sh
. . .
. . .
rootlock_2.sh		   while_demo.sh.x.c
Documents:
empty_directory:
ubuntu2404% 

这两个空目录出现在输出的底部。

最后,我只想看到空目录,如下所示:

ubuntu2404% ls *(/^F)
Documents:
empty_directory:
ubuntu2404% 

很酷吧?

接下来,让我们看看文件扩展。

扩展文件

扩展文件的方式有几种。首先,我们将根据文件或目录的文件类型进行扩展。

扩展文件和目录

如果你想查看目录中的文件,但又不想看到任何目录,可以这样做:

ubuntu2404% ls *(.)
acl_demo.sh		   rsync_password.sh
diskspace.sh		   shutdown-update.sh
encrypted_password	   sudo_demo1.sh
. . .
. . .
rootlock_1.sh		   while_demo.sh
rootlock_2.sh		   while_demo.sh.x.c
ubuntu2404% 

如果你只想看到你的目录,无论是空目录还是非空目录,可以这样做:

ubuntu2404% ls *(/)
bashlib-0.4:
bashlib     config.cache  config.status  COPYING  Makefile     README
bashlib.in  config.log	  configure	 INSTALL  Makefile.in
. . .
. . .
Documents:
empty_directory:
FallOfSudo:
fallofsudo.py  LICENSE	main.png  README.md
My_Files:
afile.txt			somefile.txt	 yetanotherfile.txtx
anotherfile.txt			somepicture.jpg  yetanotheryetanotherfile.txt
haveicreatedenoughfilesyet.txt	somescript.sh
ubuntu2404% 

好的,我想你已经明白这里发生了什么。让我们继续看一下文件权限。

通过文件权限扩展

第二十章,Shell 脚本安全性中,我解释了文件和目录权限是如何工作的。让我们快速回顾一下。

权限设置分为用户(即文件或目录的所有者)、其他。每一项都可以设置任何组合的读取、写入和执行权限。如果你需要查找具有特定权限设置的文件或目录,你可以使用 find 工具。在 zsh 中,你可以使用通配符限定符代替 find。这里是你将使用的限定符列表:

对于用户:

  • r: 用户具有读取权限。

  • w: 这表示用户具有写权限的文件。

  • x: 用户具有可执行权限。

  • U: 这会查找属于当前用户的文件或目录。

  • s: 这会查找具有 SUID 权限设置的文件。

对于组:

  • A: 该组具有读取权限。

  • I: 该组具有写权限。

  • E: 该组具有可执行权限。

  • G: 这会查找属于当前用户组的文件或目录。

  • S: 这会查找具有 SGID 权限设置的文件或目录。

对于其他人:

  • R: 其他人具有读取权限。

  • W: 其他人具有写权限。

  • X: 其他人具有可执行权限。

  • t: 这会查找具有粘滞位的目录。

那么,所有这些是如何工作的呢?假设您在/bin/目录中,并且想查看所有具有 SUID 权限的文件。只需像这样做:

ubuntu2404% cd /bin      
ubuntu2404% ls -ld *(s)  
-rwsr-xr-x 1 root root  72792 Apr  9 07:01 chfn
-rwsr-xr-x 1 root root  44760 Apr  9 07:01 chsh
-rwsr-xr-x 1 root root  39296 Apr  8 15:57 fusermount3
-rwsr-xr-x 1 root root  76248 Apr  9 07:01 gpasswd
-rwsr-xr-x 1 root root  51584 Apr  9 14:02 mount
-rwsr-xr-x 1 root root  40664 Apr  9 07:01 newgrp
-rwsr-xr-x 1 root root  64152 Apr  9 07:01 passwd
-rwsr-xr-x 1 root root  55680 Apr  9 14:02 su
-rwsr-xr-x 1 root root 277936 Apr  8 14:50 sudo
-rwsr-xr-x 1 root root  39296 Apr  9 14:02 umount
ubuntu2404% 

如果您在自己的家目录中并且想查看整个文件系统中的所有 SUID 文件,只需使搜索递归,如下所示:

ubuntu2404% ls -ld /**/*(s)
-rwsr-xr-x 1 root root        85064 Feb  6  2024 /snap/core20/2264/usr/bin/chfn
-rwsr-xr-x 1 root root        53040 Feb  6  2024 /snap/core20/2264/usr/bin/chsh
. . .
. . .
-rwsr-xr-x 1 root root       154824 Jul 26 02:32 /usr/lib/snapd/snap-confine
ubuntu2404% 

find不同,ls并非本质上是递归的。因此,如果您想在当前目录的所有下级子目录中进行搜索,只需这样做:

ubuntu2404% pwd
/home/donnie
ubuntu2404% ls -l **/*(s)
zsh: no matches found: **/*(s)
ubuntu2404% 

这与完整的文件系统搜索之间的唯一区别是,我使用了**/*(s)而不是/**/*(s)。 (当然,在我的家目录中没有 SUID 文件,这是可以预期的。)

如果您需要实际处理这个ls输出,您可以创建一个zsh脚本,像这样创建一个find_suid.sh脚本:

#!/bin/zsh
ls -ld /**/*(s) > suid_files.txt 

这只是一个简单的小脚本,它创建一个包含您系统中所有 SUID 文件的文本文件。但嘿,谁说我们不能把它弄得更花哨一些呢?让我们看看如何在find_suid2.sh脚本中实现:

#!/bin/zsh
suid_results_file=$1
if [[ -f "$suid_results_file" ]]; then
        echo "This file already exists."
        exit
fi
ls -ld /**/*(s) > "$suid_results_file" 

使用此脚本时,您需要指定要保存结果的文件名。如果该文件名已经存在,脚本会给出警告信息并退出。

您可以以相同的方式使用其他任何通配符限定符。例如,假设您需要搜索整个文件系统中属于您的文件。让我们看看是如何工作的:

ubuntu2404% ls -ld /**/*(U) | less 

这会生成一个非常长的列表,因此我必须将其管道输出到less以防止输出内容滚出屏幕顶部。以下是您将看到的一部分内容:

crw--w----  1 donnie tty    136, 0 Sep  3 20:04 /dev/pts/0
crw-------  1 donnie tty      4, 1 Sep  3 19:24 /dev/tty1
drwxr-x--- 13 donnie donnie   4096 Sep  3 20:02 /home/donnie
-rw-rw-r--  1 donnie donnie     44 Jul 24 20:53 /home/donnie/acl_demo.sh
drwxr-xr-x  2 donnie donnie   4096 Jun 29 21:10 /home/donnie/bashlib-0.4
. . .
. . .
-rw-r--r--  1 donnie donnie      0 Sep  3 19:24 /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/memory.reclaim
-rwxrwxrwx  1 donnie donnie   2155 Jul  5 19:31 /usr/bin/deshc 

正如您所见,典型的 Linux 系统中到处都有属于您的文件。

这是zsh相对于其他所有 shell(包括bash)的一个明显优势。zsh的扩展文件通配符功能可以替代find的大部分功能,但语法更简单。(当然,您也可以使用这个技巧与除了ls之外的其他实用程序一起使用,例如cpmvrmchmodchown。)

你还可以使用zsh文件通配符来替代grepawktr等工具。老实说,用于替代这些工具的通配符命令语法复杂到让人头疼,因此你最好还是使用这些工具。不过,使用zsh文件通配符会使你的脚本变得稍微轻便和更快。而且,在那些可能没有安装这些工具的罕见情况下,你还可以使用文件通配符。

无论如何,如果你想了解zsh的额外文件通配符功能,这里有一个链接:

Zsh 原生脚本手册:

github.zshell.dev/docs/zsh/Zsh-Native-Scripting-Handbook.html

你还可以通过阅读zshexpn的手册页来进一步了解文件通配符和变量扩展的相关内容。

好的,我们简要谈谈数组。

理解zsh数组

这会很快,因为我只想指出zsh数组的三点。第一点是,zsh数组的索引编号从 1 开始,而不像bash数组那样从 0 开始。下面是bash的示例:

donnie@fedora:~$ mybasharray=(vicky cleopatra horatio)
donnie@fedora:~$ echo ${mybasharray[0]}
vicky
donnie@fedora:~$ echo ${mybasharray[1]}
cleopatra
donnie@fedora:~$ 

你看到使用索引号 0 时,返回的是列表中的第一个元素,而索引号 1 则返回第二个元素。现在,在zsh中是这样的:

ubuntu2404% myzsharray=(vicky cleopatra horatio)
ubuntu2404% echo ${myzsharray[0]}
ubuntu2404% echo ${myzsharray[1]}
vicky
ubuntu2404% echo ${myzsharray[2]}
cleopatra
ubuntu2404% 

这次,索引号为 0 什么也不会返回,因为索引 0 不存在。

第二个大的区别在于如何查看整个数组的内容。使用bash时,你需要像这样做:

donnie@fedora:~$ echo ${mybasharray[@]}
vicky cleopatra horatio
donnie@fedora:~$ 

如果你愿意,你仍然可以在zsh中这样做,但你不必这样做。你可以像查看普通变量的值一样查看数组的内容,像这样:

ubuntu2404% echo $myzsharray  
vicky cleopatra horatio
ubuntu2404% 

最后,我想向你展示如何消除数组中的重复条目。为了演示,让我们创建一个包含水果列表的数组,像这样:

ubuntu2404% fruits=(orange apple orange banana kiwi banana apple orange)
ubuntu2404% echo $fruits
orange apple orange banana kiwi banana apple orange
ubuntu2404% 

如你所见,列表中有几个重复项。要去除这些重复项,只需这样做:

ubuntu2404% echo ${(u)fruits}
orange apple banana kiwi
ubuntu2404% 

(u)替换为(U)会将整个列表以大写字母打印出来。

ubuntu2404% echo ${(U)fruits}
ORANGE APPLE ORANGE BANANA KIWI BANANA APPLE ORANGE
ubuntu2404% 

uU结合使用,你就能去除重复项,并将列表以大写字母打印出来。

ubuntu2404% echo ${(uU)fruits}
ORANGE APPLE BANANA KIWI
ubuntu2404% 

为了实际展示,让我们看看fruit_array.sh脚本:

#!/bin/zsh
fruits=(apple orange banana apple orange kiwi)
echo "Let's look at the entire list of fruits."
for fruit in $fruits; do
        echo $fruit
done
echo "*****************"
echo "*****************"
echo "Now, let's eliminate the duplicate fruits."
printf "%s\n" ${(u)fruits} 

在第一个代码段中,我使用for循环打印出fruits数组中的每个水果。在第二个代码段中,我想打印出每个独立的水果,但不包括重复项。使用echo会将所有水果打印在同一行,就像我在上面的命令行示例中展示的那样。因此,我没有使用echo,而是用了printf。这里,%s参数告诉printf打印指定的文本字符串,在本例中就是数组中的每个成员。\n选项在每个水果后插入换行符。运行脚本的效果如下:

ubuntu2404% ./fruit_array.sh
Let's look at the entire list of fruits.
apple
orange
banana
apple
orange
kiwi
*****************
*****************
Now, let's eliminate the duplicate fruits.
apple
orange
banana
kiwi
ubuntu2404% 

其实你可以在数组上做更多的事情,比我在这里所能展示的要多。要了解它们,看看 zshexpn 手册页。

数组部分到此为止。那么现在,让我们来谈谈数学。

增强的数学功能

第十一章—执行数学运算 中,我解释了 bash 只有有限的内建数学功能,并且只能处理整数。对于更复杂的运算,你需要在 bash 脚本中使用外部程序,例如 bc。另一方面,zsh 内建了更强大的数学功能。

对于我们的第一个例子,让我们尝试在 zsh 命令行中将 5 除以 2:

ubuntu2404% echo $((5/2))
2
ubuntu2404% 

好吧,那不是我想要的。我应该看到最终答案是 2.5,而不是 2。发生了什么?答案是,为了执行浮动点数学,你必须明确地将其中一个数字表示为浮动点数。所以,我们来修正这个问题。

ubuntu2404% echo $((5.0/2))
2.5
ubuntu2404% 

好得多。通过将第一个数字表示为 5.0 而不是 5,浮动点数学得以正常工作。不幸的是,zsh 的数学功能并不完美。我的意思是,它在这个除法例子中工作得很好,但看看当我尝试其他任何操作时会发生什么:

ubuntu2404% echo $((2.1+2.1))
4.2000000000000002
ubuntu2404% echo $((2.1*2))
4.2000000000000002
ubuntu2404% echo $((2.1-2))
0.10000000000000009
ubuntu2404% echo $((2.1%2))
0.10000000000000009
ubuntu2404% 

出于某种我不理解的原因,zsh 在小数点后添加了额外的尾随数字。如果它们全是零倒还无所谓,但尾随的 2 和 9 让我们的计算结果出现了问题。幸运的是,我们可以通过简单地将结果赋给一个变量来轻松修正这个问题,就像这样:

ubuntu2404% ((sum=2.1+2.1))
ubuntu2404% echo $sum
4.2000000000
ubuntu2404% 

尾随的零仍然存在,但至少多余的 2 和 9 数字不见了。

即使经过了一些相当广泛的研究,我也从未找到可以用来指定小数点后显示数字位数的命令或选项开关。

分组数学运算,以指定它们的优先级,像这样写:

ubuntu2404% ((product=(2.1+2.1)*8))
ubuntu2404% echo $product        
33.6000000000
ubuntu2404% 

如果简单的数学运算不足以满足你的需求,你可以加载 mathfunc 模块,我将在接下来的章节中展示给你。

使用 zsh 模块

很多 zsh 功能包含在可加载的 zsh 模块中。我无法在本章中涵盖所有的 zsh 模块,因此我们只看一些更有用的模块。

首先,让我们来看一下默认情况下哪些模块会加载到 zsh 会话中:

ubuntu2404% zmodload
zsh/complete
zsh/computil
zsh/main
zsh/parameter
zsh/stat
zsh/terminfo
zsh/zle
zsh/zutil
ubuntu2404% 

还有很多更多可选的模块,你可以通过使用 zmodload 命令自行加载,正如我们接下来要看到的。

要查看所有可用的 zsh 模块,请参阅 zshmodules 手册页。

现在,让我们通过查看 mathfunc 模块来更具体一些。

使用 mathfunc 模块

如果你想创建一些非常复杂的数学脚本,而不需要使用像 bc 这样的外部工具,只需加载 mathfunc 模块,如下所示:

ubuntu2404% zmodload zsh/mathfunc
ubuntu2404% 

这个模块提供了三角函数、对数、指数运算以及其他一些数学函数。你可以这样查看模块提供的所有函数:

ubuntu2404% zmodload -lF zsh/mathfunc
+f:abs
+f:acos
+f:acosh
+f:asin
+f:asinh
. . .
. . .
+f:y0
+f:y1
+f:yn
ubuntu2404% 

所以现在,假设你想查看 9 的平方根。可以这样做:

ubuntu2404% ((squareroot=sqrt(9)))
ubuntu2404% echo $squareroot
3.0000000000
ubuntu2404% 

现在,让我们把这个放入 squareroot.sh 脚本中:

#!/bin/zsh
zmodload zsh/mathfunc
your_number=$1
((squareroot=sqrt(your_number)))
echo $squareroot 

请注意,我必须在此脚本的开头放置一个zmodload zsh/mathfunc命令。这是因为每次关闭终端或注销计算机时,您通过命令行加载的任何模块都会自动卸载。因此,您需要将此zmodload命令添加到脚本中。

在这里,我使用your_number变量来保存用户为$1参数指定的数字值。让我们看看它是否有效。

ubuntu2404% ./squareroot.sh 9
3.0000000000
ubuntu2404% ./squareroot.sh 25
5.0000000000
ubuntu2404% ./squareroot.sh 2
1.4142135624
ubuntu2404% 

哦,没错,它运行得很好,而且完全不需要修改任何外部程序,比如bc

其他所有mathfunc函数也以类似方式工作。运用你的想象力,以及我为bash展示的脚本概念,来创建你自己的zsh数学脚本。

这就是你对mathfunc模块的简介。接下来,我们简要看看datetime模块。

datetime模块

datetime模块的功能几乎与date命令相同。你可以使用其中任何一个做一些酷的事情,比如在文件名中创建带时间戳的文件。它默认没有加载,因此我们现在来加载它:

ubuntu2404% zmodload zsh/datetime
ubuntu2404% 

现在,让我们看看它提供了什么:

ubuntu2404% zmodload -lF zsh/datetime
+b:strftime
+p:EPOCHSECONDS
+p:EPOCHREALTIME
+p:epochtime
ubuntu2404% 

下面是你正在查看的内容的详细说明:

  • strftime:这是此模块中唯一的函数。你可以像使用date命令一样使用它。

  • EPOCHSECONDS:这个环境变量是一个整数值,表示自 Unix 纪元开始以来经过的秒数。这个变量在bash中也可用。

  • EPOCHREALTIME:这个环境变量是一个浮动值,表示自 Unix 纪元开始以来经过的秒数。根据系统的不同,这个值的精确度可能达到纳秒或微秒级别。这个变量在bash中也可用。

  • epochtime:这个变量包含两个组件。它像EPOCHREALTIME变量,只不过小数点被空格代替。这个变量在bash中不可用。

Unix 纪元从 1970 年 1 月 1 日开始。早期的 Unix 开发者决定,设置 Unix 计算机时间的最简单方法就是以自该日期以来经过的秒数来表示时间。

你可以通过以下方式查看当前三个变量的值:

ubuntu2404% echo $EPOCHSECONDS
1725470539
ubuntu2404% echo $EPOCHREALTIME
1725470545.8938724995
ubuntu2404% echo $epochtime
1725470552 124436538
ubuntu2404% 

现在,让我们创建一个使用strftime函数的zsh_time.sh脚本:

#!/bin/zsh
zmodload zsh/datetime
timestamp=$(strftime '%A-%F_%T')
echo "I want to create a file at $timestamp." > "timefile_$timestamp.txt" 

strftime函数使用与date命令相同的日期和时间格式选项。在这种情况下,我们有:

  • %A:这是星期几。

  • %F:这是以 YYYY-MM-DD 格式表示的日期。

  • %T:这是以 HH:MM:SS 格式表示的时间。

使用datestrftime的唯一区别是,使用date时,必须在格式选项字符串前加上+,像这样:

date +'%A-%F-%T' 

strftime函数有自己的手册页,你可以通过运行man strftime来查看。

在我运行脚本之后,我应该会得到我的文件:

ubuntu2404% ./zsh_time.sh                             
ubuntu2404% ls -l timefile_*                               
-rw-rw-r-- 1 donnie donnie 58 Sep  4 18:16 timefile_Wednesday-2024-09-04_18:16:37.txt
ubuntu2404% cat timefile_Wednesday-2024-09-04_18:16:37.txt
I want to create a file at Wednesday-2024-09-04_18:16:37.
ubuntu2404% 

我已经介绍了我认为最有用的模块。如果你想阅读其他zsh模块的内容,可以查看zshmodules手册页。

我认为这差不多可以作为我们对zsh脚本的介绍了。让我们总结一下,继续前进。

总结

在这一章中,我向你介绍了在zsh中编写脚本的概念,而不是在bash中编写。我从展示如何安装zsh开始,如果它还没有安装的话。我展示了bashzsh之间的差异,这些差异可能会影响你编写zsh脚本的方式。这些差异包括文件通配符、变量扩展、数组索引和数学运算的不同。在过程中,我向你展示了这些zsh特性如何帮助你编写比bash脚本更简单、更有功能的脚本。

zsh脚本的主要问题,除了bash更加普及之外,就是缺乏文档。关于设置zsh用户环境的教程和文章有很多,但关于zsh脚本的很少。希望你能利用我在这里提供的知识编写一些酷炫的zsh脚本。

在下一章,也是最后一章中,我将向你介绍如何在 Linux 上使用 PowerShell。(是的,你没看错。)我们在那里见。

问题

  1. 你会使用哪个命令来查看已加载到你的zsh会话中的所有zsh模块?

    1. zmodls

    2. zmod -l

    3. zmodload -l

    4. zmodload

  2. 你想加载数学函数模块。你会使用以下哪个命令?

    1. zmod mathfunc

    2. zmod zsh/mathfunc

    3. zmodload -l mathfunc

    4. zmodload -l zsh/mathfunc

    5. zmodload zsh/mathfunc

  3. bashzsh处理数组的主要区别是什么?

    1. 没有区别。

    2. bash数组索引从 1 开始,zsh数组索引从 0 开始。

    3. bash数组索引从 0 开始,zsh数组索引从 1 开始。

    4. bash可以处理数组,但zsh不能。

  4. 你会使用哪个zsh命令来查看目录中所有的.png文件和.PNG文件?

    1. ls *.png

    2. ls (#i).png

    3. ls (#l).PNG

    4. ls *.PNG

  5. 你只想查看当前工作目录中的空目录。你会使用以下哪个zsh命令?

    1. ls -l *(/^F)

    2. ls -l *(^F)

    3. ls -l *(F)

    4. ls -ld

进一步阅读

答案

  1. d

  2. e

  3. c

  4. b

  5. a

加入我们的 Discord 社区!

与其他用户、Linux 专家以及作者本人一起阅读这本书。

提出问题,为其他读者提供解决方案,通过“问我任何问题”(Ask Me Anything)环节与作者交流,等等。扫描二维码或访问链接加入社区。

packt.link/SecNet

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/QR_Code10596186092701843.png

第二十三章:在 Linux 上使用 PowerShell

PowerShell 最初是一个封闭源代码的专有产品,只能安装在 Windows 操作系统上。然而现在,它是自由开源软件,并且可以在基于 Linux 和 macOS 的机器上自由使用。

在本章中,我无法给你提供一整套 PowerShell 课程,因为这需要一本完整的书籍。而是,我将提供 PowerShell 哲学的高层次概述,展示如何安装它,并提供一些有用的示例。我还会说明为什么你作为 Linux 或 Mac 管理员,可能会想要学习 PowerShell。当然,你会在整个章节中以及 进一步阅读 部分找到许多 PowerShell 参考资料的链接。

本章的主题包括:

  • 在 Linux 和 macOS 上安装 PowerShell

  • Linux 和 Mac 管理员学习 PowerShell 的原因

  • PowerShell 脚本与传统 Linux/Unix 脚本的区别

  • 查看可用的 PowerShell 命令

  • 获取 PowerShell 命令的帮助

  • 实际跨平台 PowerShell 脚本

如果你准备好了,我们开始吧。

技术要求

你可以使用 Fedora、Ubuntu 或 Debian 虚拟机进行操作。你不会使用 FreeBSD 或 OpenIndiana 虚拟机,因为 PowerShell 不支持这些操作系统。

一如既往,你可以通过以下方式获取脚本:

git clone https://github.com/PacktPublishing/The-Ultimate-Linux-Shell-Scripting-Guide.git 

在 Linux 和 macOS 上安装 PowerShell

我们将首先查看在 Linux 上安装 PowerShell 的方法,然后再看看如何在 macOS 上安装它。

通过 snap 包在 Linux 上安装 PowerShell

snapd 系统是一个通用的软件打包系统,由 Ubuntu 的开发者发明。它在 Ubuntu 操作系统中默认安装,并且可以安装在大多数其他 Linux 发行版上。

如果你正在设置一个新的 Ubuntu 服务器,你可以选择从 Ubuntu 安装程序中安装一些 snap 包,包括 PowerShell。如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/B21693_23_01.png

图 23.1:在 Ubuntu 安装过程中选择 PowerShell snap 包

你可以在非 Ubuntu 发行版上安装 snapd,但是不同发行版的安装说明有所不同。你可以在这里找到相关的安装说明:

snapcraft.io/docs/installing-snapd

安装完 snapd 后,你可以通过以下方式安装 PowerShell:

sudo snap install powershell --classic 

在 Fedora 上安装 PowerShell

在 Fedora 系统上,你可以使用多种方法安装 PowerShell。例如,你可以通过 .rpm 包或 Docker 容器来安装它。你可以在这里找到每种方法的详细说明:

fedoramagazine.org/install-powershell-on-fedora-linux/

在 macOS 上安装 PowerShell

你可以通过 Homebrew 系统在 Mac 上安装 PowerShell。你可以在这里找到详细的安装说明:

learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos?view=powershell-7.4

启动 PowerShell

一旦 PowerShell 安装完成,输入 pwsh 命令来启动它。你的命令行界面会变成这样:

PS /home/donnie> 

现在,在我展示任何示范之前,我们先解决今天的燃眉之急。为什么像你这样使用 Linux 或 Mac 的人需要学习一个由微软发明的脚本语言?

Linux 和 Mac 管理员学习 PowerShell 的理由

在 PowerShell 中编写脚本与在传统的 Linux 和 Unix shell 中编写脚本有些不同。但其实并不难,一旦习惯了,你甚至可能会喜欢它。无论如何,Linux 管理员有一些合理的理由想要学习 PowerShell。我们来看看其中的一些原因。

使用混合操作系统环境

第一个理由仅仅是出于便利性和灵活性的考虑。许多企业和组织运行混合的 Linux 和 Windows 服务器,并且通常在工作站上运行 Windows。如果你能够在 Windows 和 Linux 平台上使用相同的脚本语言,可能会非常有帮助。而且,如果你是 Windows 管理员,现在需要学习 Linux 管理,PowerShell 可能会让你更容易做到,因为你可能已经掌握了它。事实上,让我告诉你我学习 PowerShell 的一个原因。

在 2010 年和 2011 年这两年里,我和一个客户合作,他将 Nagios 公司作为他的客户之一。Nagios 公司生产 Nagios 网络监控系统,可以监控几乎所有类型的网络设备。(这包括运行 Linux 或 Windows 的服务器,以及各种网络设备。)我的客户的工作分为三个部分:编写培训文档、进行 Nagios 培训课程,并飞往全国各地为 Nagios 公司的客户设置 Nagios 监控系统。

反正,每当我的客户需要对 Windows 服务器做些什么时,他都会找我,因为我懂 Windows Server,而他不懂。

好吧,我一直守着一个黑暗的秘密,希望你们不要对我有不好的看法。其实早在 2006 年,在我接触 Linux 之前,我已经获得了 Microsoft Certified Systems Engineer (MCSE) 认证,专注于 Windows Server 2003。当我第一次接触 Linux 时,我以为我再也不会用到我的 MCSE 培训了。天啊,我错得真离谱。

不幸的是,我的 MCSE 培训并没有涉及 PowerShell,因为它在那时还没有被发明出来。因此,为了为我的客户提供 Windows Server 监控解决方案,我不得不自学 PowerShell 脚本编写。

这里的关键是,如果你参与任何类型的网络监控解决方案的设置,甚至如果你是 Linux 管理员,学习 PowerShell 也会很有用。

PowerShell 命令可以更简洁

第二个理由是,在某些情况下,PowerShell 更容易处理。你已经看到,在传统的 Linux/Unix shell 语言中,执行某些任务的命令可能变得很长且复杂。例如,假设你想查看所有占用 200 兆字节或更多机器随机存取内存(RAM)的系统进程,并且你只想看到输出的某些字段。在传统的 Linux/Unix 方法中,你需要使用ps命令加上适当的选项开关,然后将ps输出传给awk,像这样:

donnie@fedora:~$ ps -eO rss | awk -F' ' '{ if($2 >= (1024*200)) {printf("%s\t%s\t%s\n",$1,$2,$6);}}'
PID	       RSS	 COMMAND
3215	338896	 /usr/lib64/chromium-browser/chromium-browser
3339	247596	 /usr/lib64/chromium-browser/chromium-browser
. . .
. . .
21502	614792	 /usr/lib64/firefox/firefox
23451	369392	 /usr/lib64/firefox/firefox
donnie@fedora:~$ 

那么,这有什么问题呢?首先,你需要熟悉ps的选项开关,以及各种ps输出字段。在这个例子中,ps -e命令会显示类似这样的内容:

donnie@fedora:~$ ps -e
    PID      TTY          TIME         CMD
      1        ?          00:00:04     systemd
      2        ?          00:00:00     kthreadd
      3        ?          00:00:00     pool_workqueue_release
. . .
. . .
    28122  ?              00:00:00     Web Content
    28229  ?              00:00:00     kworker/7:0
    28267  pts/1          00:00:00     ps
donnie@fedora:~$ 

但是,这并没有显示RSS字段,而这个字段包含了我们想要看到的内存使用数据。所以,我将添加O rss选项,这样最终的ps命令就会是ps -eO rss。现在它应该显示类似这样的内容:

PID     RSS   S TTY      TIME      COMMAND
 1      29628 S ?        00:00:04  /usr/lib/systemd/systemd --switched-root --sys
 2      0     S ?        00:00:00  [kthreadd]
 3      0     S ?        00:00:00  [pool_workqueue_release]
. . .
. . .
 27199  0     I ?        00:00:00  [kworker/8:2]
 27262  62216 S ?        00:00:00  /usr/lib64/firefox/firefox -contentproc -child
 27300  5060  R pts/1    00:00:00  ps -eO rss
donnie@fedora:~$ 

现在的问题是,这比我们想要看到的要多。将这个输出传给awk,可以过滤掉所有不需要的内容。但为了使用awk,你需要知道ps输出的每一列是什么,以便知道在awk命令中列出哪些字段。在这个例子中,我们想看到PIDRSSCOMMAND字段,它们分别是第 1 列、第 2 列和第 6 列。我们想查看所有第 2 列大于 200 兆字节的进程,在这里我们将其表示为(1024*200)

请记住,兆字节的真正定义是 1024 千字节。由于awk无法理解字节、千字节、兆字节等单位,你必须将内存测量表达为数学公式或正常整数。(1024*200 的整数结果是 204800。)

最后,printf命令会按照正确的格式选项打印出我们想要查看的字段。

好的,这是可行的。但是,我们能用 PowerShell 简化一下吗?让我们看看。

PS /home/donnie> Get-Process | Where-Object WorkingSet -ge 200MB
 NPM(K)    PM(M)      WS(M)     CPU(s)    Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
      0     0.00     331.27      40.27    3215 …10 chromium-browser --enable-n…
      0     0.00     227.35      11.00    3674 …10 chromium-browser --type=ren…
      0     0.00     221.48      15.93    3369 …10 chromium-browser --type=ren…
      0     0.00     228.37      19.19    3588 …10 chromium-browser --type=ren…
. . .
. . .
      0     0.00     402.61     271.87    8737 …10 soffice.bin
      0     0.00     971.86   1,004.20    5869 …12 VirtualBoxVM
PS /home/donnie> 

是的,我觉得这样更简单了。它更简短,这样也不容易出错。你只需要知道WorkingSet (WS)字段,它相当于ps中的RSS字段。此外,你可以看到,我们可以将一个命令(Get-Process)的输出传递给另一个命令(Where-Object),就像我们在 Linux/Unix 中将一个工具的输出传给另一个工具一样。最棒的是,这个命令非常直观,我认为即使我不解释,你也能理解它在做什么。

好的,让我们继续学习 PowerShell 的下一个理由。

增强的内建数学功能

如果你需要创建大量数学计算的脚本,PowerShell 可能正是你需要的。你可以在不加载任何外部程序的情况下进行浮点数学运算,并且提供了一个函数库来支持高级数学函数。让我们看一些例子。

首先,让我们做一个简单的除法操作,像这样:

PS /home/donnie> 5 / 2
2.5
PS /home/donnie> 

如你所见,PowerShell 默认进行浮点数学运算,不需要调用任何特殊技巧。现在,让我们在math1.ps1脚本中加入一些不同的数学题目,它看起来是这样的:

param([Float]$number1 = "" , [Float]$number2 = "")
$sum = $number1 + $number2
Write-Host "The sum of these numbers is: " $sum
Write-Host "***********"
$divideresult= $number1 / $number2
Write-Host "Division of these two numbers results in: "$divideresult
Write-Host "***********"
$multiplyresult = $number1 * $number2
Write-Host "Multiplying these two numbers results in: " $multiplyresult
Write-Host "***********"
$modulo = $number1 % $number2
Write-Host "The remainder from dividing these two numbers is: " $modulo 

这里首先要注意的是顶部的param行。这是用来创建位置参数的指令,我将用它将参数传递到脚本中。我没有使用$1$2作为位置参数,而是用param来创建$number1$number2的位置参数。注意我只用一个param指令就创建了这两个位置参数。如果我使用两个单独的param行,脚本会出错,因为它无法识别第二行param。另外,我已经指定了number1number2这两个用于位置参数的变量类型是浮点数。

剩下的脚本是一个简单的演示,展示如何进行加法、除法、乘法和取模操作。我没有使用echoprintf语句,而是使用了 PowerShell 的本地命令Write-Host。另外,请注意,无论是定义变量还是调用其值,变量名前面都会加上$符号。现在,让我们使用 5 和 2 作为参数来运行脚本:

PS /home/donnie> ./math1.ps1 5 2
The sum of these numbers is:  7
***********
Division of these two numbers results in:  2.5
***********
Multiplying these two numbers results in:  10
***********
The remainder from dividing these two numbers is:  1
PS /home/donnie> 

够简单吧?好吧,在 PowerShell 中做更复杂的数学计算也同样容易。为了演示,我们创建math2.ps1脚本,如下所示:

param([Float]$number1 = "")
$tangent = [math]::Tan($number1/180*[math]::PI)
Write-Host "The tangent of $number1 degrees is:  $tangent."
Write-Host "***********"
$cosine = [math]::Cos($number1/180*[math]::PI)
Write-Host "The cosine of $number1 degrees is: "$cosine
Write-Host "***********"
$squareroot = [math]::Sqrt($number1)
Write-Host "The square root of $number1 is: " $squareroot
Write-Host "***********"
$logarithm = [math]::Log10($number1)
Write-Host "The base 10 logarithm of $number1 is: " $logarithm 

在这里,你可以看到我如何使用[math]::结构来调用计算正切、余弦、平方根和对数的函数。唯一的小问题是,正切和余弦函数默认使用弧度。如果想用角度来计算,我需要将输入的度数除以 180,然后再乘以π的值。

现在,让我们用 45 作为参数运行这个脚本:

PS /home/donnie> ./math2.ps1 45
The tangent of  45  degrees is:  1.
***********
The cosine of 45 degrees is:  0.7071067811865476
***********
The square root of 45 is:  6.708203932499369
***********
The base 10 logarithm of 45 is:  1.6532125137753437
PS /home/donnie> vim 

哦,太好了,效果不错。

你可以在这里阅读更多关于 PowerShell 数学的内容,包括它的数学函数的完整列表:ss64.com/ps/syntax-math.html

ss64.com/ps/syntax-math.html

接下来,让我们看看 PowerShell 与传统 Shell 的一些基本区别。

PowerShell 脚本与传统 Linux/Unix 脚本的区别

PowerShell 仍然使用许多与其他脚本语言相同的编程结构,如函数、循环、if结构等。不过,正如你已经看到的,PowerShell 在基础设计上也有一些不同之处,使其与传统的 Linux/Unix Shell 脚本有所区别。让我们来看几个例子。

使用文件名扩展名和可执行权限

在本书中,你看到我创建了带有 .sh 扩展名的普通 Linux/Unix 脚本。实际上,在普通的 Linux/Unix 脚本中你不必使用任何文件扩展名。这意味着 somescript.sh 脚本只要文件名为 somescript 也能正常工作。但是对于 PowerShell 脚本,.ps1 扩展名对所有 PowerShell 脚本都是必需的。没有这个扩展名,你的 PowerShell 脚本将无法运行。

另一方面,不必像传统的 Linux/Unix 脚本那样设置 PowerShell 脚本的可执行权限。只要脚本文件名以 .ps1 结尾,并且你在 PowerShell 环境中,脚本就会运行。

PowerShell 是面向对象的

传统的 Linux 和 Unix shell 脚本语言是严格的过程式语言。这意味着你创建的程序或脚本中的命令将处理来自某些外部来源的数据。

这些源可以是键盘、文件、数据库,甚至是嵌入在脚本或程序中的here文档。请理解数据和处理数据的命令是完全独立的实体。

面向对象编程中,有时会看到称为OOP的东西,数据和操作数据的过程被打包在一个对象中。这使你能够做一些很酷的事情,比如告诉一个数值对象将自己加倍。它还允许你创建从其父对象继承特性的其他对象。

我知道这只是面向对象编程的简化解释。但是,详细的解释超出了本书的范围。当然,如果你曾经在像 C++ 或 Java 这样的语言中编程过,你会明白我在说什么。

PowerShell 使用命令 Cmdlets

大多数 PowerShell 命令实际上被称为Cmdlets,你会发音为command-lets,而其他一些则是别名或函数。每个 Cmdlet 的形式为由连字符分隔的动词和名词,例如 Get-Content 用于从文件获取(读取),或者 Set-Content 用于向文件写入。在 Windows 机器上的工作方式是每个 Windows 子系统都有自己的一组 Cmdlets。例如,作为域名系统DNS)服务器的 Windows 机器会有一组用于处理 DNS 的 Cmdlets,而Microsoft Exchange服务器则会有用于处理 Exchange 的 Cmdlets。

PowerShell 的 Linux 和 Mac 版本拥有较少的 Cmdlets 集合,因为所有的 Windows 特定内容都被剔除了。你会找到适用于 Linux 或 macOS 的所需内容,但其他内容则没有了。

如你所见,这些 Cmdlets 与 Linux/Unix 管理员习惯使用的命令有很大不同。因此,你可能考虑使用别名来帮助顺利过渡到 PowerShell。让我们接着来看看这个。

在 PowerShell 上使用别名

在 Windows 机器上,PowerShell 已经设置了别名,允许你使用 Linux/Unix 命令替代 PowerShell 命令。(微软这样做是为了让 Linux/Unix 管理员更容易学习 PowerShell。)例如,以下是如何在 PowerShell for Linux 上列出当前目录中文件的方式:

PS /home/donnie> Get-ChildItem
    Directory: /home/donnie
UnixMode         User Group    LastWriteTime       Size Name
--------         ---- -----    -------------       ---- ----
drwxr-xr-x     donnie donnie   6/29/2024 21:10     4096 bashlib-0.4
drwx------     donnie donnie   7/4/2024 23:59      4096 demo_files
. . .
. . .
-rw-rw-r--     donnie donnie   9/3/2024 22:26      67 zsh_fruits.txt
-rwxrw-r--     donnie donnie   9/4/2024 18:17      141 zsh_time.sh
PS /home/donnie> 

你会看到 Get-ChildItem 命令几乎与 ls -l 命令执行相同的工作。在 Windows 机器上,已经有一个 PowerShell 别名允许你使用 ls 命令来运行 Get-ChildItem 命令。以下是在我其中一台 Windows 机器上的效果:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/B21693_23_02.png

图 23.2:在 Windows 上使用 ls PowerShell 别名

所以,是的,这看起来就像 Get-ChildItem 命令。但是,如果你在 Linux 或 Mac 上打开 PowerShell 会话并运行 ls 命令,你不会调用别名,因为默认情况下没有设置别名。相反,你将调用来自你的 Linux 或 Mac 操作系统的实际 ls 命令。以下是它的效果:

PS /home/donnie> ls
acl_demo.sh		   rootlock_1.sh
bashlib-0.4		   rootlock_2.sh
date_time.sh		   rsync_password.sh
. . .
. . .
hello.sh		   zsh_fruits.txt
My_Files		   zsh_time.sh
rootlock_1a.sh
PS /home/donnie> 

你会发现这看起来一点也不像 Get-ChildItem。实际上,这只是你普通 ls 命令的正常输出。

幸运的是,你可以在 PowerShell 上创建自己的别名,这可以使得输入 PowerShell 命令更加容易。你可以使用 Set-AliasNew-Alias 命令来创建别名。让我们首先创建一个别名,让 ls 命令运行 Get-ChildItem 命令,使用 Set-Alias

PS /home/donnie> Set-Alias -Name ls -Value Get-ChildItem
PS /home/donnie> ls
    Directory: /home/donnie
UnixMode         User Group         LastWriteTime         Size Name
--------         ---- -----         -------------         ---- ----
drwxr-xr-x     donnie donnie      6/29/2024 21:10         4096 bashlib-0.4
drwx------     donnie donnie       7/4/2024 23:59         4096 demo_files
. . .
. . .
-rw-rw-r--     donnie donnie       9/3/2024 22:26           67 zsh_fruits.txt
-rwxrw-r--     donnie donnie       9/4/2024 18:17          141 zsh_time.sh
PS /home/donnie> 

另一种方法是使用 New-Alias,如下所示:

PS /home/donnie> New-Alias ls Get-ChildItem
PS /home/donnie> 

无论哪种方式都能正常工作,但这只是一个临时修复。一旦关闭 PowerShell 会话,你将失去这个别名。要使你的别名永久生效,你需要编辑你的 PowerShell 配置文件。你可以通过以下方式找到它的位置:

PS /home/donnie> echo $profile
/home/donnie/.config/powershell/Microsoft.PowerShell_profile.ps1
PS /home/donnie> 

唯一的问题是 .config/ 目录是存在的,但 powershell/ 子目录和 Microsoft.PowerShell_profile.ps1 文件都不在。因此,你需要创建它们。

你可以像这样使用你的正常 Linux/Mac 命令来创建 powershell/ 子目录:

PS /home/donnie> mkdir .config/powershell
PS /home/donnie> 

然后,使用你喜欢的文本编辑器在新的 powershell/ 子目录中创建 Microsoft.PowerShell_profile.ps1 文件。添加以下行以创建 lsGet-ChildItem 的别名:

New-Alias ls Get-ChildItem 

你可以在这个文件中添加任意多个别名。只需记得退出你的 PowerShell 会话,然后重新登录以确保新配置文件生效。

重要提示:如果你打算创建在多台机器上运行的 PowerShell 脚本,请注意你创建的别名可能不会在某些机器上可用。在这种情况下,你最好放弃使用自己的别名,而是直接使用原生的 PowerShell 命令。

接下来,让我们看看如何查看可用的 PowerShell 命令。

查看可用的 PowerShell 命令

要查看可用的命令,只需执行:

PS /home/donnie> Get-Command     
CommandType     Name               Version    S
                                                                        					                           o
                                                                           					                           u
                                                                        					                           r
                                                                        					                           c
                                                                        					                           e
-----------     ----               -------    -
Alias           Get-PSResource     1.0.4.1    M
Function        cd..                                                        
Function        cd\                                                         
Function        cd~                                                         
Function        Clear-Host                                                  
Function        Compress-Archive   1.2.5      M
. . .
. . .
Cmdlet          Write-Verbose      7.0.0.0    M
Cmdlet          Write-Warning      7.0.0.0    M
PS /home/donnie> 

CommandType 下,你会看到别名、函数和 Cmdlet。在 Source 列下,你会看到字母 M,这意味着这些命令都存储在某个 PowerShell 模块中。

当然,Linux 上的 PowerShell 命令远没有 Windows 上的多。要查看有多少个命令,只需执行以下命令:

PS /home/donnie> Get-Command | Measure-Object | Select-Object -Property Count
Count
-----
  293
PS /home/donnie> 

在这里,我们首先将 Get-Command Cmdlet 的输出通过管道传输到 Measure-Object Cmdlet。根据帮助文件,Measure-Object 的官方解释是它计算某些类型对象的属性值。在这个例子中,我们只想查看其中的一项计算,显示有多少个命令。我们通过将 Measure-Object 的输出管道传输到 Select-Object -Property Count 来实现这一点,它与 Linux/Unix 中的 wc -l 命令作用相同。经过这些操作,我们看到这台 Linux 机器上有 293 个可用的 PowerShell 命令。

正如我在 PowerShell 使用 Cmdlets 部分中提到的,所有 Cmdlet 都是 动词-名词 格式。你可以使用 Get-Command,并结合 -Verb-Noun 选项,查看某个特定动词或名词的所有命令。例如,让我们查看与 Date 名词相关的所有可用命令:

PS /home/donnie> Get-Command -Noun 'Date'
CommandType     Name          Version    S
                                         o
                                         u
                                         r
                                         c
                                         e
-----------     ----          ----    -
Cmdlet          Get-Date      7.0.0.0    M
Cmdlet          Set-Date      7.0.0.0    M
PS /home/donnie> 

我们看到 Date 只有两个 Cmdlet。让我们看看 Set 动词有多少个:

PS /home/donnie> Get-Command -Verb 'Set'
CommandType     Name                 Version    S
                                                o
                                                u
                                                r
                                                c
                                                e
-----------     ----                 -------    -
Function        Set-HostnameMapping  1.0.1      H
Function        Set-PSRepository     2.2.5      P
Cmdlet          Set-Alias            7.0.0.0    M
Cmdlet          Set-Clipboard        7.0.0.0    M
. . .
. . .
Cmdlet          Set-StrictMode       7.4.5.500  M
Cmdlet          Set-TraceSource      7.0.0.0    M
Cmdlet          Set-Variable         7.0.0.0    M
PS /home/donnie> 

现在,你可能在想如何找出这些 PowerShell 命令都做什么。好吧,让我们接着看。

获取 PowerShell 命令帮助

这个很简单。只需使用 Get-Help Cmdlet,后跟你需要了解信息的命令名称。例如,你可以这样查看 Get-Module Cmdlet 的信息:

PS /home/donnie> Get-Help Get-Module 

你将看到的将类似于普通的 Linux/Unix 手册页。

在每个 Get-Help 屏幕的底部,你会看到一些命令,告诉你如何查看更多的信息。例如,如果你需要查看如何使用 Get-Module Cmdlet 的示例,可以这样做:

PS /home/donnie> Get-Help Get-Module -Examples 

有时候,你需要学习的命令的帮助页面可能不可用。在这种情况下,可以运行 Update-Help Cmdlet,或者将 -Online 选项附加到 Get-Help 命令的末尾。

另一个很好的资源是 PowerShell 命令页面,你可以在这里找到:ss64.com/ps/

(当然,很多列出的命令是 Windows 专用的,但你也会发现不少是跨平台的。)

好的,我想这就足够解释 PowerShell 的理论了。让我们来看几个实际的 PowerShell 脚本示例。

跨平台 PowerShell 脚本的实际应用

为了找出一些跨平台的 PowerShell 脚本的实际应用示例,我在 DuckDuckGo 上搜索了 PowerShell 脚本示例 Linux 这个文本字符串。我发现的最酷的东西就是 Github 上的 Mega Collection of PowerShell Scripts。你可以通过执行以下命令使用 git 下载它:

git clone https://github.com/fleschutz/PowerShell.git 

下载完成后,进入PowerShell/scripts/目录,看看那里面有什么。作为我们的第一个例子,让我们看一个简单的示例,它可以在所有平台上运行。

write-marquee.ps1 脚本

write-marquee.ps1 脚本真正实现了跨平台,因为它没有使用任何特定于某一操作系统的命令。它所做的只是创建一个在屏幕上流动的跑马灯消息。我不能在这里展示整个脚本,但你可以查看你本地的副本。那么,让我们只看它的各个部分,看看它是如何设置的。

在脚本的顶部,你会看到注释部分,这部分被<##>包围。实际上,这些不仅仅是注释。如果你执行Get-Help ./write-marquee.ps1,你会看到<##>之间的所有内容会显示为帮助屏幕,像这样:

PS /home/donnie/PowerShell/scripts> Get-Help ./write-marquee.ps1
NAME
    /home/donnie/PowerShell/scripts/write-marquee.ps1
SYNOPSIS
    Writes text as marquee
. . .
. . .
REMARKS
    To see the examples, type: "Get-Help /home/donnie/PowerShell/scripts/write-marquee.ps1 -Examples"
    For more information, type: "Get-Help /home/donnie/PowerShell/scripts/write-marquee.ps1 -Detailed"
    For technical information, type: "Get-Help /home/donnie/PowerShell/scripts/write-marquee.ps1 -Full"
    For online help, type: "Get-Help /home/donnie/PowerShell/scripts/write-marquee.ps1 -Online"
PS /home/donnie/PowerShell/scripts> 

接下来,param([string]$Text = 这一行创建了一个包含跑马灯消息的字符串类型变量。它看起来像这样:

param([string]$Text = "PowerShell is powerful - fully control your computer! PowerShell is cross-platform - available for Linux, Mac OS and Windows! PowerShell is open-source and free - see the GitHub repository at github.com/PowerShell/PowerShell! PowerShell is easy to learn - see the tutorial for beginners at guru99.com/powershell-tutorial.html! Powershell is fully documented - see the official PowerShell documentation at docs.microsoft.com/en-us/powershell", [int]$Speed = 60) # 60 ms pause 

变量定义末尾的[int]$Speed = 60) # 60 ms pause部分决定了跑马灯消息在屏幕上流动的速度。

接下来的StartMarquee函数是这样的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/B21693_23_03.png

图 23.3:StartMarquee 函数

在绘制跑马灯框并使用$LinePos行确保所有内容正确定位后,你会看到foreach循环,它会一字一字地将消息打印到屏幕上。

最后,在函数定义之后,你会看到函数调用:

StartMarquee "                                                                                    +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++ $Text +++                                                                                         
"
exit 0 # success 

函数调用将包含跑马灯消息的$Text变量传递给StartMarquee函数。在每次迭代之间,$Text消息将插入三个加号符号(+++)。脚本会一直运行,直到遍历完所有的$Text消息。无论如何,下面是我在 Ubuntu Server 虚拟机上运行时的效果:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/B21693_23_04.png

图 23.4:运行 write-marquee.ps1 脚本

很酷吧?现在,让我们看看更酷的东西。

check-cpu.ps1 脚本

check-cpu.ps1 脚本尝试检索机器 CPU 的型号和名称,并获取 CPU 温度。

如果你想查看 CPU 温度,你需要直接在主机 Linux 机器上运行这个脚本,而不是在虚拟机中运行。在虚拟机中运行将无法让脚本访问主机的温度传感器。

我说“尝试”,因为当我在我的 Fedora 工作站上运行这个脚本时,显示的结果是这样的:

PS /home/donnie/PowerShell/scripts> ./check-cpu.ps1
✅ 64-bit CPU (16 cores, ) - 48°C OK                                                                                  
PS /home/donnie/PowerShell/scripts> 

所以,它只显示我正在运行某种 64 位 CPU,且有 16 个核心。实际情况是,这台机器运行的是英特尔® Xeon® CPU E5-2670,具有八个核心并启用了超线程。但是,如果我在 Windows 10 机器上运行相同的脚本,我会看到这样的结果:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/B21693_23_05.png

图 23.5:Windows 机器上的 check-cpu.ps1 脚本

在这台古老的(大约 2008 年的)戴尔 Windows 10 Pro 机器上,脚本正确地报告 CPU 是旧款 Core 2 Quad,型号为 Q9550。而另一方面,脚本在 Linux 机器上正确地报告了 CPU 温度,但在 Windows 机器上则没有。这个部分很容易解释。只是一些计算机主板,特别是旧的主板,带有的温度传感器不兼容现代操作系统。

至于正确获取 CPU 型号,似乎 Linux 的 PowerShell 命令做不到这一点,而 Windows 的命令则可以。我们来看看脚本,看看发生了什么。

check-cpu.ps1脚本的顶部,你会看到GetCPUArchitecture函数,它是这样的:

function GetCPUArchitecture {
        if ("$env:PROCESSOR_ARCHITECTURE" -ne "") { return "$env:PROCESSOR_ARCHITECTURE" }
        if ($IsLinux) {
                $Name = $PSVersionTable.OS
                if ($Name -like "*-generic *") {
                        if ([System.Environment]::Is64BitOperatingSystem) { return "x64" } else { return "x86" }
                } elseif ($Name -like "*-raspi *") {
                        if ([System.Environment]::Is64BitOperatingSystem) { return "ARM64" } else { return "ARM32" }
                } elseif ([System.Environment]::Is64BitOperatingSystem) { return "64-bit" } else { return "32-bit" }
        }
} 

正如你所见,这里的语法与正常的 Linux/Unix 脚本有些不同。一个区别是,PowerShell 用括号而不是方括号来包围测试条件。例如这样:

if ("$env:PROCESSOR_ARCHITECTURE" -ne "") { return "$env:PROCESSOR_ARCHITECTURE" } 

这一行的作用只是返回PROCESSOR_ARCHITECTURE环境变量的值,如果该变量存在的话。这个函数的其余部分专门用于 Linux,正如if (IsLinux)语句块所证明的。你可以看到它在检测 CPU 是否为 x86、x86_64 或 ARM。对于 ARM,它会检测是 32 位版本还是 64 位版本。

这个函数中 Windows 使用的唯一部分是第一行,即if ("$env:PROCESSOR_ARCHITECTURE" -ne "") { return "$env:PROCESSOR_ARCHITECTURE" }这一行。而且,在我的 Windows 10 机器上,这行代码唯一的作用是将“AMD64”字符串插入到你在上面图形中看到的信息里。在 Windows 上,所有其他的 CPU 信息都是通过Windows 管理工具WMI)接口获取的。这部分代码位于try语句块的else分支中,从第 44 行开始。(else分支从第 54 行开始。)它看起来是这样的:

try {
        Write-Progress "Querying CPU status..."
        $status = "✅"
        $arch = GetCPUArchitecture
        if ($IsLinux) {
                $cpuName = "$arch CPU"
                $arch = ""
                $deviceID = ""
                $speed = ""
                $socket = ""
        } else {
                $details = Get-WmiObject -Class Win32_Processor
                $cpuName = $details.Name.trim()
                $arch = "$arch, "
                $deviceID = "$($details.DeviceID), "
                $speed = "$($details.MaxClockSpeed)MHz, "
                $socket = "$($details.SocketDesignation) socket"
        } 

if..else结构中,你可以看到如果机器运行的是 Linux,它将调用GetCPUArchitecture函数并从中获取 CPU 信息。在else部分,你看到的是 Windows 的代码。尽管脚本通过PROCESSOR_ARCHITECTURE环境变量获取了 Windows 的一些信息,但大多数 CPU 信息是通过Windows 管理工具WMI)子系统获取的。事实上,PROCESSOR_ARCHITECTURE变量没有包含我这台特定 Fedora 工作站的任何信息。所以,在这台机器上,我只能得到一个关于我运行的是 32 位还是 64 位机器,或者是 x86 还是 ARM 机器的通用消息。虽然这样,但没关系,因为我可以修改脚本来解决这个问题。

在编辑器中打开check-cpu.ps1脚本,向下滚动直到你看到try语句块中的这一行,它应该是第 47 行:

$arch = GetCPUArchitecture 

我们会保持这一行不变,因为 Windows 也使用GetCPUArchitecture函数。相反,我们将在if ($IsLinux) {这一行之后,即第 48 行,添加如下新行:

$arch = Get-Content /proc/cpuinfo | Select-String "model name" | Select-Object -Unique 

现在,arch变量的值不是通过GetCPUArchitecture函数获取,而是通过命令替换构造获取。

我们在这里看到了 PowerShell 脚本的几个不同之处。

首先,每次使用 PowerShell 变量时,都需要在变量名之前加上$。这意味着无论是定义变量还是调用其值,都需要加上$。其次,你会看到,在 PowerShell 中,命令替换构造不需要用$( )括起来。相反,直接像平常那样写命令即可。

好的,让我们分解这行代码。

  • Get-Content:这与 Linux/Unix 的cat命令完成了相同的工作。

  • Select-String:这与 Linux/Unix 的grep命令完成了相同的工作。

  • Select-Object -Unique:这与 Linux/Unix 的uniq命令完成了相同的工作。我们需要在这里使用它,因为我的机器有 16 个虚拟核心,而我只想看到其中一个的相关信息。

在我的 Fedora 工作站上运行修改后的脚本是这样的:

PS /home/donnie> ./check-cpu.ps1
⚠️model name    : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz CPU (16 cores, ) - 57°C HOT                                
PS /home/donnie> 

这样好多了,但还不完全完美。我真想去掉输出开头的“model name”文本字符串。让我们再对脚本进行一次小改动,像这样:

$longarch = Get-Content /proc/cpuinfo | Select-String "model name" | Select-Object -Unique
$arch = $longarch -replace "model name\t: ", "" 

我在这里做的只是将原始的$arch行中的$arch改为$longarch。我会将这个作为我的中间变量。然后,我添加了一行新的$arch,使用-replace "model name\t: ", ""命令来删除“model name”字符串以及随后的制表符、冒号和空格。在这种情况下,-replace命令与 Linux/Unix 的sed命令完成了相同的工作。为了更好理解,这就是修改后的try语句块的相关部分:

try {
        Write-Progress "Querying CPU status..."
        $status = "✅"
        $arch = GetCPUArchitecture
        if ($IsLinux) {
                $longarch = Get-Content /proc/cpuinfo | Select-String "model name" | Select-Object -Unique
                $arch = $longarch -replace "model name\t: ", ""
                $cpuName = "$arch CPU"
                $arch = ""
                $deviceID = ""
                $speed = ""
                $socket = ""
        } 

运行新脚本的结果是这样的:

PS /home/donnie> ./check-cpu.ps1
⚠ Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz CPU (16 cores, ) - 62°C HOT                                                
PS /home/donnie> 

完美。这正是我想看到的,除了我的工作站似乎有点过热的原因。(明天,我会打开它,看看是否需要吹掉一些灰尘。)最棒的是,这仍然是一个跨平台脚本,因为我没有做任何可能影响它在 Windows 上运行的事情。

好的,我想这就是我们对 PowerShell 奥秘的介绍。让我们总结一下,然后结束这部分内容。

总结

在本章的最后,我向你展示了在 Linux 机器上使用 PowerShell 脚本的基本情况。我从历史背景开始,然后介绍了 PowerShell 脚本的哲学,以及它与传统 Linux/Unix Shell 脚本的区别。你还了解了如何使用 PowerShell 命令,以及如何获取命令的帮助。最后,我向你展示了一些实际的 PowerShell 脚本示例,这些脚本可以在 Windows 或 Linux 上运行。但正如我之前所说,单单一章的内容只能提供 PowerShell 脚本的高层概述。如果你想深入了解,可以通过大量的在线资源和书籍来进一步学习。

哦,我差点忘了最重要的事。我还向你解释了作为 Linux 或 Mac 管理员,你可能会考虑使用 PowerShell 脚本的一些理由。是的,它确实不同,需要一些适应。但它确实有一些明显的好处,特别是如果你需要在 Linux、macOS 和 Windows 混合环境中工作时。

这不仅是第二十三章的结束,也是本书的结束。在整个过程中,你从一个命令行初学者成长为一个 Shell 脚本大师。当然,你可能无法记住所有的内容,但没关系。只要你能将本书以及其他任何可以找到的资源作为参考,当你需要创建一个出色的脚本时,随时可以翻阅。

无论如何,这是一段漫长的旅程,但我很享受这过程,也希望你也一样。保重,也许我们会在某个时候再见。

进一步阅读

留下您的评论!

感谢您购买 Packt 出版的这本书——我们希望您喜欢它!您的反馈至关重要,能帮助我们改进和成长。请花点时间留下一个亚马逊评论;它只需一分钟,但对像您这样的读者来说意义重大。

扫描下面的二维码,获取您选择的免费电子书。

packt.link/NzOWQ

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/review.png

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/New_Packt_Logo1.png

packt.com

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及帮助您规划个人发展并推动职业发展的行业领先工具。欲了解更多信息,请访问我们的网站。

为什么要订阅?

  • 用来自 4000 多位行业专家的实用电子书和视频,减少学习时间,增加编程时间

  • 通过为您特别设计的技能计划提升您的学习

  • 每月免费获得一本电子书或视频

  • 完全可搜索,便于访问关键信息

  • 复制、粘贴、打印和书签内容

www.packt.com,您还可以阅读一系列免费的技术文章,注册各种免费的通讯,并获得 Packt 书籍和电子书的独家折扣和优惠。

其他您可能喜欢的书籍

如果您喜欢这本书,您可能会对 Packt 出版的其他书籍感兴趣:

https://www.packtpub.com/en-in/product/linux-kernel-programming-9781803232225

Linux 内核编程

Kaiwan N. Billimoria

ISBN: 9781803232225

  • 从源代码配置并构建 6.1 LTS 内核

  • 为 6.x 内核编写高质量的模块化内核代码(LKM 框架)

  • 探索现代 Linux 内核架构

  • 掌握内核中关于内存管理的关键内部细节

  • 理解并使用各种动态内核内存分配/释放 API

  • 发现关于内核中 CPU 调度的关键内部方面,包括 cgroups v2

  • 更深入理解内核并发问题

  • 学习如何使用关键的内核同步原语

https://www.packtpub.com/en-in/product/the-software-developers-guide-to-linux-9781804616925

Linux 软件开发人员指南

David Cohen, Christian Sturm

ISBN: 9781804616925

  • 学习有用的命令行技巧和工具,使软件开发、测试和故障排除变得轻松

  • 了解 Linux 和命令行环境的实际工作原理

  • 创建强大、定制化的工具,并通过以开发者为中心的 Linux 工具节省成千上万行代码

  • 通过 Docker、SSH 和 Shell 脚本任务获得实践经验,让你成为更高效的开发者

  • 熟练掌握在 Linux 服务器上搜索日志和排查问题

  • 处理那些让其他开发者头疼的常见命令行问题

Packt 正在寻找像你这样的作者

如果你有兴趣成为 Packt 的作者,请访问 authors.packtpub.com 并立即申请。我们与成千上万的开发者和技术专业人士合作,帮助他们将自己的见解分享给全球技术社区。你可以进行通用申请,申请我们正在招聘的特定热门话题的作者,或者提交你自己的想法。

加入我们的 Discord 社区!

和其他读者、Linux 专家以及作者本人一起阅读本书。

提问、为其他读者提供解决方案、通过问我任何问题(Ask Me Anything)环节与作者互动,等等。扫描二维码或访问链接加入社区。

packt.link/SecNet

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ult-linux-shscp-gd/img/QR_Code10596186092701843.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值