第一章:小试牛刀

1.1 在终端中显示输出

​ 用户是通过终端会话同she1l环境打交道的。如果你使用的是基于图形用户界面的系统,这指的就是终端窗口。如果没有图形用户界面(生产服务器或SSH会话),那么登录后你看到的就是shell提示符。

​ 在终端中显示文本是大多数脚本和实用工具经常需要执行的任务。she1l可以使用多种方法和格式显示文本。

1.1.1 预备知识

​ 命令都是在终端会话中输人并执行的。打开终端时会出现一个提示符。有很多方法可以配置提示符,不过其形式通常如下:

username@hostname$
[youzipii@localhost ~]$

或者也可以配置成root@hostname #,或者简单地显示为$或#。

$表示普通用户,#表示管理员用户root。root是Linux系统中权限最高的用户。

注意:

​ 因为如果shell(root)具备较高的权限,命令中出现的输入错误有可能造成更严重的破坏所以推荐使用普通用户(shel1会在提示符中以s来表明这种身份)登录系统,然后借助sudo这类工具来运行特权命令。使用sudo执行命令的效果和root一样。

shell脚本通常以shebang起始:

#!/bin/bahs

​ shebang是一个文本行,其中#!位于解释器路径之前。/bin/bash是Bash的解释器命令路径,bash将以#符号开头的行视为注释。脚本中只有第一行可以使用shebang来定义解释该脚本所使用的解释器。

脚本的执行方式有两种。

(1)将脚本名作为命令行参数:

​ bash myScript.sh

(2)授予脚本执行权限,将其变为可执行文件:

​ chmod 755 myscript.sh

​ ./myScript.sh.

​ 如果将脚本作为bash的命令行参数来运行,那么就用不着使用shebang了。可以利用shebang来实现脚本的独立运行。可执行脚本使用shebang之后的解释器路径来解释脚本。

​ 使用chmoa命令赋予脚本可执行权限:
​ $ chmod a+x sample.sh

​ 该命令使得所有用户可以按照下列方式执行该脚本:

​ $ /sample.sh #./表示当前目录
或者

​ $ /home/path/sample.sh #使用脚本的完整路径
​ 内核会读取脚本的首行并注意到shebang为#!/bin/bash。它会识别出/bin/bash并执行该脚本:
​ $ /bin/bash sample.sh
​ 当启动一个交互式she1l时,它会执行一组命令来初始化提示文本、颜色等设置。这组命令来自用户主目录中的脚本文件/.bashrc(对于登录shel则是/.bash profle)。Bash shell还维护了一个历史记录文件~/.bash history,用于保存用户运行过的命令。

~:表示主目录,通常是/home/user 其中user是用户名,如果是root则为/root

图形化环境创建的终端不会读取profile或bash_profile(绝大多数情况下),而ssh登陆远程系统时会读取profile。shell使用分号或换行符来分隔单个命令或命令序列。比如:

$cm1 ; cmd2

这等同于

$cmd1

$cmd2
注释部分以#为起始,一直延续到行尾。注释行通常用于描述代码或是在调试期间禁止执行某行代码·:
#sample.sh-echoes “hello world”
echo “hello world”

1.1.2 练习

echo用于终端打印的最基本命令。

默认情况下,echo在每次调用后会添加一个换行符:

[youzipii@localhost ~]$ echo "Welcome o bash"
Welcome o bash

​ 只需要将文本放人双引号中,echo命令就可以将其中的文本在终端中打印出来。类似地不使用双引号也可以得到同样的输出结果.

​ 需要注意的是这些方法看起来相似,但各有特定的用途及副作用。双引号允许she1l解释字符串中出现的特殊字符。单引号不会对其做任何解释。

​ 如果需要打印像!这样的特殊字符,那就不要将其放入双引号中,而是使用单引号,或是在特殊字符之前加上一个反斜线():但我试了下好像都可以

​ 另一个可用于终端打印的命令是printf。该命令使用的参数和C语言中的printf函数一样。
例如:

#!/bin/bash
#文件名: printf.sh

printf "%-5s %-10s %-4s\n" No Name Mark
printf "%-5s %-10s % -4.2f\n" 1 Sarath 80.3456

image-20240412201042680

1.1.3 工作原理

​ %s、%c、%d和%f都是格式替换符(fommat substitution character ),它们定义了该如何打印后续参数。-5s指明了一个格式为左对齐且宽度为5的字符串替换(-表示左对齐)。如果不指明-,字符串就采用右对齐形式。宽度指定了保留给某个字符串的字符数量。对Name而言,其保留宽度是10。因此,任何Name字段的内容都会被显示在10字符宽的保留区域内,如果内容不足10个字符,余下的则以空格填充。

​ 对于浮点数,可以使用其他参数对小数部分进行舍人(round of)。

​ 对于Mark字段,我们将其格式化为$-4.2f,其中.2指定保留两位小数。注意,在每行的格式字符串后都有一个换行符(\n )。

1.1.4 补充内容

​ 使用echo和printf的命令选项时,要确保选项出现在命令中的所有字符串之前,否则Bash会将其视为另外一个字符串。

  1. 在echo中转义换行符

​ 默认情况下,echo会在输出文本的尾部追加一个换行符。可以使用选项-n来禁止这种行为。echo同样接受双包含转义序列的双引号字符串作为参数。在使用转义序列时,需要使用echo -e”包含转义序列的字符串”这种形式。例如:

image-20240412201415626

  1. 打印彩色输出

​ 脚本可以使用转义序列在终端中生成彩色文本。

​ 文本颜色是由对应的色彩码来描述的。其中包括:重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37。

image-20240412201531119

​ 其中\e[1;31m是一个转义字符串,可以将颜色设为红色,\e[0m将颜色重新置回。只需要将31替换成想要的色彩码就可以了。
​ 对于彩色背景,经常使用的颜色码是:重置=0,黑色=40,红色=41,绿色-42,黄色-43蓝色=44,洋红=45,青色=46,白色=47。

​ 要设置彩色背景的话,可输入如下命令:
image-20240412201653480

这些例子中包含了一些转义序列。可以使用man console_codes来查看相关文档。

1.2 使用变量与环境变量

​ 所有的编程语言都利用变量来存放数据,以备随后使用或修改。和编译型语言不同,大多数脚本语言不要求在创建变量之前声明其类型。用到什么类型就是什么类型。在变量名前面加上一个美元符号就可以访问到变量的值。shell定义了一些变量,用于保存用到的配置信息,比如可用的打印机、搜索路径等。这些变量叫作环境变量

1.2.1 预备知识

​ 变量名由一系列字母、数字和下划线组成,其中不包含空白字符。常用的惯例是在脚本中使用大写字母命名环境变量,使用驼峰命名法或小写字母命名其他变量。

​ 所有的应用程序和脚本都可以访问环境变量。可以使用env或printenv命令查看当前shell中所定义的全部环境变量:

image-20240412201913135

要查看其他进程的环境变量,可以使用如下命令:

sudo cat /proc/$PID/environ

其中,PID是相关进程的进程ID(PID是一个整数)。我们可以使用pgrep或ps -aux | grep 进程名令获得进程ID:

image-20240412202225249

image-20240412202332320

​ 特殊文件/proc/PID/environ是一个包含环境变量以及对应变量值的列表。每一个变量以name=value的形式来描述,彼此之间由null字符(\0)分隔。形式上确实不太易读。

要想生成一份易读的报表,可以将cat命令的输出通过管道传给tr,将其中的\0替换成\n:
$cat /proc/5588/environ | tr ‘\0’ ‘\n’

image-20240412202522980

1.2.2 练习

​ 可以使用等号操作符为变量赋值:
​ varName=value
​ varName是变量名,value是赋给变量的值。如果value不包含任何空白字符(例如空格),那么就不需要将其放入引号中,否则必须使用单引号或双引号。

注意:var =value不同于var=value。把var=value写成var =value是一个常见的错误。两边没有空格的等号是赋值操作符,加上空格的等号表示的是等量关系测试。

在变量名之前加上美元符号($)就可以访问变量的内容。

#!/bin/bash
var="value"
echo $var

image-20240412203000936

我们可以在printf、echo或其他命令的双引号中引用变量值:

#!/bin/bash
fruit=apple
count=5
echo "We have $count ${fruit}{s}"

image-20240412210750534

​ 因为shell使用空白字符来分隔单词,所以我们需要加上一对花括号来告诉shell这里的变量名是fruit,而不是fruit(s)。

​ 环境变量是从父进程中继承而来的变量。例如环境变量HTTP_PROXY,它定义了Intermet连接应该使用哪个代理服务器。

​ 该环境变量通常被设置成:
​ HTTP PROXY=192.168.1.23:3128
​ export HTTP_PROXY

​ export命令声明了将由子进程所继承的一个或多个变量。这些变量被导出后,当前shell脚本所执行的任何应用程序都会获得这个变量。she1l创建并用到了很多标准环境变量,我们也可以导出自己的环境变量。
​ 例如,PATH变量列出了一系列可供she1l搜索特定应用程序的目录。一个典型的PATH变量包含如下内容:

image-20240412211049043

​ 各目录路径之间以:分隔。$PATH通常定义在/etc/environment、/etc/profile或~/.bashrc中。如果需要在PATH中添加一条新路径,可以使用如下命令:

image-20240412211154589

另外还有一些众所周知的环境变量:HOME、PWD、USER、UID、SHELL等。

image-20240412211252443

​ 使用单引号时,变量不会被扩展( expand),仍依照原样显示。这意味着s echo$var’会显示svar。
​ 但如果变量$var已经定义过,那么secho”svar”会显示出该变量的值;如果没有定义过,则什么都不显示。

1.2.3 补充内容

shell还有很多内建特性。下面就是其中一些。

  1. 获得字符串的长度
#!/bin/bash
var=1234567890
echo ${#var}

image-20240412225416201

  1. 识别当前所使用的shell

echo $0或echo $SHELL

image-20240412225512242

  1. 检查是否为超级用户

​ 环境变量UID中保存的是用户D。它可以用于检查当前脚本是以root用户还是以普通用户的身份运行的。例如:

#!bin/bash
if [ $UID -ne 0 ]; then
echo Non root user, please run as root.
else
echo Root user
fi

image-20240412230241644

  1. 修改Bash的提示字符串(usernamechostname:~s)

​ 当我们打开终端或是运行shell时,会看到类似于user@hostname:/home/s的提示字符串。不同的GNUIimux发布版中的提示字符串及颜色各不相同。我们可以利用ps1环境变量来定义主提示字符串。默认的提示字符串是在文件~/.bashrc中的某一行设置的。
​ 还有一些特殊的字符可以扩展成系统参数。例如:\u可以扩展为用户名,\h可以扩展为主机名,而\w可以扩展为当前工作目录。

牛客shell练习

编写一个shell脚本以输出一个文本文件nowcoder.txt中的行数
示例:
假设 nowcoder.txt 内容如下:

#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}

你的脚本应当输出:
9

示例1

编写一个shell脚本以输出一个文本文件nowcoder.txt中的行数
示例:
假设 nowcoder.txt 内容如下:

输入:

#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}

输出:

9

题解:

wc命令

cat nowcoder.txt | wc -l
wc -l < nowcoder.txt
wc -l nowcoder.txt | gawk '{print $1}'
[youzipii@localhost ~]$ cat test1.c | wc -l
9
[youzipii@localhost ~]$ wc -l < test1.c
9
[youzipii@localhost ~]$ wc -l test1.c | gawk '{print $1}'
9

awk命令

awk '{print NR}' ./nowcoder.txt |tail -n1
[youzipii@localhost ~]$ awk '{print NR}' ./test1.c |tail -n1
9

awk 本身就可以只打印最后一行,因此一个 awk 脚本也可以搞定

[youzipii@localhost ~]$ awk 'END{print NR}' ./test1.c
9

使用 grep 搜索 "",然后利用 grep 自带的功能统计行

[youzipii@localhost ~]$ grep -c "" ./test1.c
9

Sed

[youzipii@localhost ~]$ sed -n '$=' ./test1.c
9

打印文件的最后5行

tail -n 5 test1.c

SHELL3 输出 0 到 500 中 7 的倍数

#! /bin/bash
seq 0 7 5

seq 用于生成从一个数到另一个数之间的所有整数。

#!/bin/bash
for i in {0..500}
do
if [[ i%7 -eq 0 ]];then
echo $i
fi
done

SHELL4 输出第5行的内容

编写一个bash脚本以输出一个文本文件nowcoder.txt中第5行的内容。

示例:
假设 nowcoder.txt 内容如下:

welcome
to
nowcoder
this
is
shell
code

你的脚本应当输出:
is

awk 'NR==5{print $0}' test.txt	

SHELL5 打印空行的行号

描述

编写一个shell脚本以输出一个文本文件nowcoder.txt中空行的行号(空行可能连续,从1开始输出)

示例:
假设 nowcoder.txt 内容如下:

a
b

c

d

e


f

你的脚本应当输出:

3
5
7
9
10
awk '/^\s*$/ {print NR}' test.txt

/^\s*$/ 是一个正则表达式,用于匹配特定的字符串模式。在这个正则表达式中:

  • ^ 表示匹配字符串的开始。
  • \s 是一个特殊字符,用于匹配任何空白字符,包括空格、制表符、换页符等。
  • * 表示前面的字符(在这里是 \s)可以出现零次或多次。
  • $ 表示匹配字符串的结束。

因此,/^\s*$/ 匹配的是一个字符串,这个字符串只包含空白字符(或者根本没有字符,即空字符串)。例如:

  • “”(空字符串)
  • “ “(一个空格)
  • “\t”(一个制表符)
  • “ \t\n”(空格、制表符和换行符的组合)

但是,它不会匹配包含任何非空白字符的字符串,如 “hello” 或 “123”。

SHELL6 去掉空行

写一个 bash脚本以去掉一个文本文件nowcoder.txt中的空行
示例:
假设nowcoder.txt 内容如下:

abc

567


aaa
bbb



ccc

你的脚本应当输出:

abc
567
aaa
bbb
ccc
awk NF
sed '/^\s*$/d' nowcoder.txt

SHELL7 打印字母数小于8的单词

写一个bash脚本以统计一个文本文件nowcoder.txt中字母数小于8的单词。
示例:
假设 nowcoder.txt 内容如下:

how they are implemented and applied in computer

你的脚本应当输出:
how
they
are

and

applied

in

说明:
不用担心你输出的空格以及换行的问题

#!/bin/bash  

# 假设单词由空白字符(空格、制表符、换行符等)分隔
awk '{
for (i = 1; i <= NF; i++) {
# 使用length函数获取字段长度(即单词长度),并检查是否小于8
if (length($i) < 8) {
print $i
# 如果你只是想计数而不是打印单词,可以取消注释以下行,并注释掉print $i
# count++
}
}
# 如果你在计数,可以在这里打印总数
# print "Total count:", count
# count = 0 # 重置计数器(如果需要处理多个文件或多次调用)
}' nowcoder.txt

# 如果你要计数并打印总数,可以稍微修改上面的脚本并取消注释相关行

SHELL8 统计所有进程占用内存百分比的和