0%

shell 快速入门教程

前言

之前做一个软件的 demo ,考虑了一下直接编写太亏,所以使用 shell 教程结合几个软件出草稿。在进行实际操作。

本篇教程最开始由其它编程语言跨维而来,再开始 style 不是那么的 shell,在此特别感谢 debain 群各位大佬的建议,以及 Boyuan 菊苣的指导。虽然说编程原理相通但是细节不尽相同,如果你发现本教程中有什么错误的地方欢迎指正。

在学习本篇教程之前,我们首先明晰编程,的最核心概念,变量 条件 循环。既然是快速入门那么我们了解这几个概念就行了。关于函数,并发之类的我们一概不讲。除开上面最核心的三个概念,其它的概念,一般是为了我们高效的操作计算机,或者高效的编写代码而准备的。如果你觉得现在的概念已经满足不了你的需求了,那就可以开始进进一步学习更加高级的概念了。不过我建议 shell 脚本学到这里就可以了,学很高级的概念不如学去学 python,这样收益更高。

每一个热门编程语言都有它适合做的事,用适合的编程语言,做适合的事是非常爽的,当然学习一门新语言也是有成本的,所以有时候也会用不那么适合的语言去完成需求。

在我的理解中,shell 脚本是 胶水 是修正液,同时联合多个软件或者对其它开发者开发的软件进行非入侵式的修改以满足自己的需求。

那么什么样的人会使用shell脚本那那肯定是长期在使用终端的人会使用 shell 脚本。

学习本教程之前 需要你使用过终端,至少需要对 Linux 的基本命令有所了解。比如 ls cat cd 至少要知道做什么的。

shell 脚本就是命令行的堆砌,在堆砌的命令中加了一点魔法,让它变得更加强大。

本教程的所有代码,建议运行的时候进行抄写而不是复制粘贴,因为大脑会偷懒!。

变量

你可以将变量想象当成一个容器,用来容纳数据。

比如:你叫什么名字。然后我回答我的名字,这里的我的名字就可以称为变量。不同的人拥有不同的名字这里的 我的名字 就可以称为变量。

不多bb直接上代码,这将有助于理解变量,但不是变量最常用的方式,稍后我会讲解代码的详细定义。

1
2
3
4
5
#! /usr/bin/env bash

read -p "What's your name? : " -r name

echo "hello $name"

将以上代码保存为 hello.sh 然后赋予可执行权限 chmod +x hello 然后运行它 ./hello 在打印 What’s your name? 后输入你的名字,类似于这样

$ ./test.sh
What's your name?  :  niconiconi
hello niconiconi

你可以多运行几次,然后输入不同的名字,看看程序是不是对不同的名字打招呼了

这里对 刚才的代码进行讲解

#! /usr/bin/env bash 这一句在文本的第一行作用是指定运行脚本所用到的解释器,当然我们也可以不写它然后运行的时候选择解释器运行它。不过为了方便建议写上。别问我为什么和别人的写法不一样,我只能说这种写法更好。

而变量最主要的作用是当我们编写程序时数据可能还不存在,我们需要一个填充位来代替它。已进行后面的代码编写。

read -p "What's your name? : " -r name 先输出一段字符串 What's your name? : ,然后将我们输入的名字放入变量 name .这里的输入可以是任意值,所以我们可以说,变量是一个容器。

echo "hello $name" 这里就是我我们的代码最后打招呼的部分了,这里我们还是使用了, echo 命令进行输出,你可能注意到了,这里我们的名字替代了 name,没错变量的意义就是,用一个固定的 名称替代一个可变的名称。如果不能替代,那我们是不是要先知道别人的名字才能打招呼啊。在 name 的前面还有一个符号 $ 这个符号的作用就是将数据从变量中取出来(取变量)如果没有这个符号那么 name 就是 name 不会变成我们的名字。你可以将代码中的 $ 删掉试一下,看结果会怎么样。

在上面的例子中我使用了取变量操作,但是只使用了一次取变量。我们可以多次使用取变量操作,来完成数据的拼接。

#! /usr/bin/env bash

name="niconiconi"
age=9
echo  "I am $name, $age years old."

具体拼接几次就看你的实际需求了。

数据类型

shell 脚本是没有数据类型,shell 一切数据皆字符串。

开篇我们说过 shell 是命令行的堆砌。我我们的数据最终都会化为一个一个的参数去调用命令(bool 是直接调用命令)。所以 shell 并不关心数据类型。关心数据的是命令。命令对输入参数是有要求的。使用最多的就是字符串,如果涉及数学运行的话。那么命令会要求输入数字,还有就是决定程序要不要执行的状态。这里可以对应上其他编程语言的三个类型 字符型(siring) 整型(int)布尔型(bool)。 对应类型只是为了为了方便理解,但是要记住 shell 是没有类型的。

shell 使用等号 = 进行赋值,当然我们也可以使用命令进行赋值,例如上面问名字的例子就是是用的命令进行赋值。不过 = 是 shell 中最常用的的一种赋值方式。例如:name="niconiconi"。等号右边的称为数据,等号左边的称为变量,这一个句子称为赋值。赋值操作操作后 name 里装的 niconiconi 就称为值。

shell 中的赋值,等号两边不能有空格类似于这样 name ="niconiconi" name= "niconiconi" `name = “niconiconi” 都是不行的。如果你学习过其它语言请不要按其它语言的写法来,请遵守 shell 的规范。

string

前面说过 shell 没有类型一切皆使用字符串(string)

string 使用双引号 "" 进行包裹,"" 中间的都是字符串,类似于这样 name="niconiconi",这里的 niconiconi 变量持有的数据,使用 "" 包裹是为了强调这个变量装的是作为字符串使用。与其它用途的字符串进行区分。

类似于这样

#! /usr/bin/env bash

name="niconiconi"

echo  $name

输出结果

$ ./test.sh
niconiconi

在 shell 中 string 不止一种写法还可以写作 name='niconiconi' 这里声明的变量使用的单引号,被单引号括起来的数据失去了特殊能力,也就是转义比如之前上面讲的 $ 就是一个特殊符号用来取变量。但它被 单引号包裹时也就失去了这一种特殊能力。比如我们用双引号和单引号来分别输出一下环境变量 PATH,来查看一下他们的区别。echo "PATH"echo $PATH。将这两句代码保存为代码在运行。直接在终端下运行几乎没有区别。

#! /usr/bin/env bash

echo  "$PATH"

双引号的输出结果

test@Tokyo:~$ echo "$PATH"
/home/test/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
test@Tokyo:~$ 

将双引号改成单引号

#! /usr/bin/env bash

echo '$PATH'

单引号的输出结果

test@Tokyo:~$ ./test.sh 
$PATH
test@Tokyo:~$

最后一种写法也就是命令行一下最常用的写法,不带引号的写法,例如:echo niconiconi 。但是不建议在 shell 脚本中使用这种写法。因为 shell 一切数据皆以字符串进行储存,然而一些字符串有特使用途。如 true 或一些数字。为了防止混淆,建议在声明的时候直接使用引号进行强调。

string 一般用来输出信息,或者在我们的脚本中,用来用来调用程序,将程序需要的数据,装在变量里去调用这样做是很有用的,在讲的到循环的时候我会讲。

int

前面说活 shell 是没有类型了。但是具体的命令是需要特定类型的数据的。比如我们进行数学运算。在上数学课的时候我们可以说我们写下的数字是字符,也可以说它是数字在进行计算的时候他就是数字。没进行计算的时候可以是字符也可以是数字。这里的 int 就有这种感觉。在就行运算的时候它就是数字,在没进行运算前可以当成字符串。

可以进行运算的不止整数(int)对吧,还有小数(float)对吧,但是这里只是演示运算所以就不讲小数了,需要的可以自己查。

shell 的数学计算不止一种写法,可以使用 epxr bc let 等命令,但是在使用他们的时候需要注意 乘法使用的符号 * 在命令行中会转义。为了防止它转义需要写作 \* ,这里我讲一下 bash 内置的数学运算的写法。

这种写法是双括号(())写法,双括号写法,只支持整数运算,整数运算就是,整个运算的公式都是整数,如果计算的结果是小数,会直接丢弃小数部分,下面是例子。

#! /usr/bin/env bash

a=5
b="2"
#b=2

c=$(($a / b))
#c=$((a / b)

echo $c

运行结果

$ ./test.sh
2

这里的变量 a 使用的不带引号的赋值方式,而 b 使用带双引号嗯呢的赋值方式是为了表达,shell 中作用有数据均使用字符串进行储存。一般字符串用途的数据使用双引号进行强调以进行区分是很有必要的。然后再双括号类进行计算 (($a / b)) ,这里你可能发现了,一个我使用了取变量操作,一一个没有,在算式中不建议使用取变量操作,直接使用变量或数字即可(上面注释了的代码就是规范代码)。这里进行了一个除法操作。并将除法操作取出来放入了变量 c ,最后使用 echo 输出计算结果。你可能发现了 5 除以 2 结果不是 2.5 么怎么结果怎么是 2 了。那是因为整数运算结果也是整数后边的小数部分直接丢弃了。

语句中的 / 是运算符 除法,除此之外还有 乘法(*) 减法(-) 加法(+)。

bool

bool 只有两种状态 true 和 false,可以从字面意思理解为真(true)和假(false),条件成立即为真,条件不即为假,也可以直接定义真假,true 和 false 实际上是对 /bin/true /bin/flase 的引用,这两个只做一件事,设置退出状态码,在 Linux 下每一个程序执行完毕都返回一个退出码给调用调用者,执行成功一般返回 0 ,执行出现问题返回非 0 值。/bin/true 设置退出状态码为 0/bin/flase 设置退出状态码为 1。只是 bash 将这两个命令内置了,使用不加引号的字符串 truefalse 代替(条件判断时生效),并根据状态码来判断真假,以方便我们的使用。

如果有其它语言基础(比如 python)的话,直接带入其它语言的就行了。没有 shell 解释那么复杂。用法是一样的。

bool 一般作用于条件和循环,在后面的章节会讲他们的用法。

/bin/true 执行结果

niconiconi@Tokyo:~$  foo=true; $foo; echo $?
0

/bin/false 执行结果

niconiconi@Tokyo:~$ ber=false;$ber; echo $?
1

条件

条件建立在变量之上,在程序运行中,我们可能会遇到不同的情况,而我们需要对这些不同进行处理以保证我们的程序能正常运行。

在没有条件之前我们的程序就是一个流程,不懂变通。只会固定的做某事,就像闭着眼睛走路一样,如果没有红绿灯那是没有什么问题的。而条件就是用来处理走路遇到红绿灯这种情况,因为我们就算是走同一条线路会遇到红绿灯不同的情况。

比如我们下载一个文件,如果文件存在于本地那我们是不是就可以不用下载了,这里的存在与不存在就可以抽象为 bool 的两种状态 true 和 false。在程序中的 条件你可以理解为如果,以一个状态来决定要不要做某事。

下面我将通过一个例子来为大家讲解 bool 和 条件。

比如说: 我们去水果店卖橘子,水果店需要有橘子,那我们才能买到,如果没有的话那就买不到了,这里的有或没有就可以看成 bool 值,有=true 没有=false。有的话我们是不是要买啊,这里的买就可以写入 if 语句块中,如果没有,用柠檬代替也行,那么买柠檬这个操作就放入 elif 中,要是柠檬也没有,用其他的代替也行,那么在下面在加 elif 就行了,从上往下判断的。要是有了橘子,那么柠檬是不会买的, 条件只会从满足的多个条件中调比较靠上的执行。如果我们要买的东西都没有那么还可以加一个默认操作 else ,不加也行随便你。记得要在条件的最后加上 fi(倒过来的 if) 表示本次条件结束了。

那么就基于水果店的这个例子写代码。

#! /usr/bin/env bash

orange=true

if $orange
then
    echo "Selling oranges"
fi

echo "end"

这是只买橘子的情况,orange=true 这里我直接定义了橘子的存在,你也可以改成 false 试一下看看 if 条件是否会在执行。if $orange 这里判断橘子是否存在, then 后面是条件成立后执行的操作,条件不成立会执行 else 后面的的操作者我会在稍后讲。else 后面不用跟 then 。当本个条件结束时使用 fi 收尾。最后使用 echo 输出 end 以方便你将 orange=true 改成 orange=false后查看执行流程。这段代码只判断了橘子是否存在,橘子不存在我们也不会进行其他的操作。

接下来演示,橘子不存在卖柠檬的操作。

#! /usr/bin/env bash

orange=false
lemon=true

if $orange
then
    echo "orange"
elif $lemon
then
    echo "lemon"
fi

echo "end"

这里我们将 orange 的变量改成了 false ,这样的话条件语句第一个 if 就会不成立,接着判断第二个 elif,第二个条件是成立的所以就会执行它,elif 你可以理解为一个条件中的其他路径,也就是一个条件判断中,除了第一个写成 if,其他的都写成 elif。当然这里我只写你两个判断条件。你也可以写更多在后面加 elif 就好了。类似于这样

#! /usr/bin/env bash

orange=false
lemon=flase
foo=true

if $orange
then
    echo "orange"
elif $lemon
then
    echo "lemon"
elif $foo #第二个 elif,当然你也可以加更多
then
    echo "foo"
fi

echo "end"

然后是橘子与柠檬都存在的情况,也就是多个条件满足优先选择靠前的的语句进行执行

#! /usr/bin/env bash

orange=true
lemon=true

if $orange
then
    echo "orange"
elif $lemon
then
    echo "lemon"
fi

echo "end"

这里两个条件都满足,但是会优先选择靠前的条件进行执行,以下是输出。

$ ./test.sh
orange
end

还有一种情况是所有的条件都不满足那么程序相当于跳过了这个条件判断,我们将上面的例子所有变量改成 false 就会跳过这一条件语句块

#! /usr/bin/env bash

orange=false
lemon=false

if $orange
then
    echo "orange"
elif $lemon
then
    echo "lemon"
fi

echo "end"

这段代码会直接输出 "end" 因为没有条件被满足。

最后也就是条件中的 else 语句了,else 用在一个条件中所有判断都不满足的时候使用,也就是当所有条件都不满足然而我们还需要做点什么的时候使用

#! /usr/bin/env bash

orange=false
lemon=false

if $orange
then
    echo "orange"
elif $lemon
then
    echo "lemon"
else
    echo "emmm"
fi

echo "end"

这里判断中的所有操作条件都不满足,所以执行了 else 中的语句,输出了 emm,唉~ 偌大一个水果店什么都没有。else 用到的情况比较少,一般用在条件满足就不用操作,而条件不满足就需要操作的情况下。条件满足或条件不满足都要操作的情况一般放在 条件语句块自外。 比如上面例子中的echo "end"。就是放在条件语句块外的。比如我们一个程序只能输入一二三,结果用户硬是要输四五六。或者一些其他的数据。那么这些不合格的数据我们直接在 else 中处理掉了。在讲循环的时候会再讲一次。这次不懂的话没关系。

比较操作

在条件中除了直接判断变量的 truefalse 还可以对两个数据的关系进行比较,这里的数据可以是变量也可以是实际的值,比较也就是数学意义上的 相等 不等于 大于 小于,还有一些比较的逻辑操作符。因为用到操作符的机会比较少,这里我就不讲了,但要知道有这种操作。

在比较时建议对变量加上双引号,防止发生意想不到的转义情况。

比较操作使用 bash 内置语法,单中括号 [] 和 双中括号 [[]] 这里只讲单中括号的使用,双中括号的作用是在,在单中括号的基础上增加了逻辑操作符的判断。

单中括号本质上是 bash 内建的 test 工具,只是用双括号来代替了命令 test

#! /usr/bin/env bash

num=1

if [ "$num" -eq 1 ]
then
    echo "true"
fi

等价于

#! /usr/bin/env bash

num=1

if test "$num" -eq 1
then
    echo "true"
fi

下面是 test 的操作符以及意义

-eq 等于
-gt 大于
-lt 小于
-ge 大于或等于
-le 小于或等于

test 除了对字符或数字进行判断还可以对文件系统进行判断,这里就不讲的,感兴趣的可以自己查询资料。

相等判断

先将相等判断是因为这个判断在比较操作中比较常用,例如:我们知道 一等于一在 shell中写作 [ 1 -eq 1 ],这里的判断语句由中括号包裹,因为 shell 中的比较语法就是这样的,括号的两边还存在两个空格,不加这两个空格语法是错误的。shell 使用 -eq 来测试是否等于,想不到吧!当然也可以替换为其他编程语言常用的 == 模式不过不建议,因为同样的代码有可能,在 Linux 上没事,换到 Windows 下的 Git bash 上有可能就炸了。

OK 上例子

#! /usr/bin/env bash

age=9

if [ "$age" -eq 9 ]
then
    echo "age = 9"
fi
echo "end"

以下是输出结果

$ ./test.sh
age = 9
end

这里我使用了一个变量与一个真值进行比较,因为 shell 中不存在常量所以我一般都这这么干,变量也是可以替代常量的,大不了不修改它就是了。不过这里演示的代码比较少。所以我就这么干了。虽然使用了变量代替实际值,变量 age 持有的值是 9 ,所以 9 是等于 9 的,条件成立然后执行了条件下的代码,你可以将 age 的变量改成其它的数字试一下看看执行结果。

不等判断

既然有相等就会有不等判断,将相等的 -eq 改成不等的 -ne 就行,在其他编程语言中不等于写作 !=

#! /usr/bin/env bash

age=10

if [ "$age" -eq 9 ]
then
    echo "age = 9"

elif [ "$age" -ne 9 ]
then
    echo "age != 9"
fi
echo "end"

这里我在上面的例子中将 age 改成了 10,并且新加了一个不等于判断,以下是输出结果

$ ./test.sh
age != 9
end

age 等于9么,很明显不等于,age不等于9么,是的。所以执行了第二个语句,这里我将等于放在前面是因为,等于出现的概率一般来说说没有 不等于出现的概率大,如果是多个判断的话,作用域更广的放前面会出现问题。

大小于判断

一般来说,等于操作使用频率要高于其他几个判断,不过这里还是讲一下。要知道有这么一操作。

在 shell 脚本中 大于 写作 -gt ,其他编程语言一般写作 > ,小于写作 -lt ,其他语言写作 <

ok 直接上例子

#! /usr/bin/env bash

read -p "My age? :  " -r age

if [ "$age" -eq 9 ]
then
    echo "猜对了"

elif [ "$age" -lt 9 ]
then
    echo "猜小了"

elif [ "$age" -gt 9 ]
then
    echo "猜大了"
fi

echo "end"

这里我再次使用了 read 从用户输入中读取变量。当用户输入小于 9 是会提示用户,输大了。大于 9 时提示用户,输大了。只有 等于 9 时才会提示输入正确。

循环

shell 有三种循环,for while untiluntil这里就不讲了,因为 untilwhile 的反操作。

for 循环

shell 的 for 循环就是一个迭代操作,那么什么是迭代操作了,先上代码再解释

#! /usr/bin/env bash

for i in $(seq 0 10)
do
    echo "$i"
done

这里使用了单括号语句 $(),如果语句中有代码(命令)使用单括号进行包裹,那么会优先执行这一句。这里执行了 seq 生成了 11 个数字。然后使用 for 的迭代操作( in )将 seq 生成的 0 到 10 的数字一次一个的放入 i 然后执行 do 和 done 中间的代码。seq 生成对少个 for 就执行多少次直到迭代完为止,我感觉这个操作适合迭代数组,也就是类型中我没有讲的一个。为什么不讲是因为使用频率不高,而且我将 shell 当成胶水,直接用文件代替了数组。

既然用到了 $() 就不得不说一下反引号,使用反引号包裹的命令和使用 $()的执行方式基本相同,都是先执行它们包裹的命令然后将它们的返回值附加到当前命令,再执行当前命令,其区别就是双引号,会对反斜杠 \ 进行转义,我们在命令行中使用反斜杠就是为了防止转义的,结果双引号对防止了反斜杠转义后面的内容,这是很不符合预期的。 $() 是新式的写法更符合我们的操作习惯。

#! /usr/bin/env bash

echo "print `echo \$HOSTNAME`"
# 这里的反斜杠,没有按预期的对 $ 进行转义

改用 $() 后

#! /usr/bin/env bash

echo "print $(echo \$HOSTNAME)"
# 对 $ 进行了转义结果与预期相同

for的另外一种就是作为计数器了。

#! /usr/bin/env bash

for ((i=0; i<10; i++))
do
    echo "$i"
done

这个代码也是执行 10 次,在for中初始 i 为 0,如果 i 小于 10 则 i++i++ 是自增语法,意思为 i + 1 写成 i++ 是因为这样比较好写。

while 循环

while 循环使用的频率应该是几种循环当中最高的了,既然称 shell 为胶水语言,那么链接两个或多个程序,肯定是必要操作了,我一般将一个程序输出到文件然后将文件一行一行的读出来喂给其他程序。比如有一个软件一次只能扫描一个 IP,然而我们要扫描一个IP段这是我就会写一个转化程序,将 IP段转化为 IP 然后再使用 shell的 while 读出来调用它。这个不讲,毕竟涉及了 IP 。

先演示一下最常用的读文件,这应该是最常用的循环用法了。

首先创建一个文件,方便我们后边的演示 echo -e "aa\nbb\ncc" > test.txt,然后编写读文件的代码,读取文件方式一行一个

#! /usr/bin/env bash
    
while read -r line
do
    echo "$line"
done < ./test.txt

这里使用了 read 命令读取成功返回 true 读完了返回 false (循环也是基于 bool 来决定要不要继续运行的),并且将读取的数据放入变量 line,最后有一个重定向操作,将我们要读取的文件重定向到循环里处理。dodone 写我们自己的代码,打算怎么使用这些数据。这里我打印了一下就完事了。

除了在循环的第一句判断循环是否继续我们也可以通过 break 来控制 while 要不要继续。

如果说 条件是做某事的话,那么循环就是一直做某事。在循环中我们可以加入条件,在条件满足时退出(break)循环。就像我们上班一样,时间到了就下班,或者说上课,时间到了就下课,这里决定循环的条件就是时间。当然循环也有不用结束的时候也就是死循环,一般用在web服务,或者定时服务。在 shell 中应该是定时服务多一点。应该也没有人用 shell 做web 吧。在使用死循环的时候,所处理的数据一定要是堵塞式的,不然脚本占用资源会暴增,然后脚本崩溃。

在上面条件猜年龄的例子中,不管我们猜的对错程序都会退出,这里我们使用 while 改进一下,只有猜对了才能退出。如果你想提前退出请按 CTRL + c 退出

#! /usr/bin/env bash

 while true
 do
    read -p "My age? :  " -r age

    if [ "$age" -eq 9 ]
    then
        echo "猜对了"
        break

    elif [ "$age" -lt 9 ]
    then
        echo "猜小了"

    elif [ "$age" -gt 9 ]
    then
        echo "猜大了"
    fi
done

echo "end"

在猜错的情况下,这个循环会一直运行。因为是一个死循环。只有在猜对的情况下回执行 break 直接跳出 while 循环。如果我们的程序字接受某几个输入,那么这种方式是很有用的。

#! /usr/bin/env bash
    
 while true
 do
    read -p "只能输入 1 或 2  :  " -r data
    
    if [ "$data" -eq 1 ]
    then
        echo "你输入的是一"
        break
    
    elif [ "$data" -eq 2 ]
    then
        echo "你输入的是一"
        break
    
    else [ "$data" -lt 9 ]
        echo "你输入的是 $data,只能输入 1 或 2"
    fi

done
    
echo "end"

死循环一定要有东西堵住,上面的两个例子使用的 read 在用户没有输入前都是堵塞的。

#! /usr/bin/env bash

foo=0

 while true
 do
    sleep 1
    ((foo++))
    echo $foo

done

这例子是一个死循环,使用的 sleep 进行堵塞,在堵塞结束后,foo 使用自增操作,最后打印它。

管道与重定向

程序的核心概念就是 输入数据(input)处理数据(程序本身的逻辑代码)到输出数据(output)的过程。

输入输出数据的方式有很多种,比如读取网络数据或者读取本地的文件,还有就是将其他程序的输出做为当前程序的的输入等等。与之对应的输出也有很过种方法,具体采用哪一种方式来进行输入和输出就看实际需求了。

在我们编写脚本的过程中有时候会用到管道与重定向。所以这一个概念我在这里也讲一下。

在我们使用终端的时候时候可可能最常用到的就是 grepgrep 是一个强大的文本过滤工具,它使用正则表达式搜索文本,并将匹配的数据进行打印。这里 grep 读取的输入就是其他程序的输出 我们使用管道 | 将它们联系起来。

例如:

history | grep psql

使用上面的两个命令查我们我在历史中使用过的所有 psql 命令因为 psql 的参数太多了懒得重写一遍,所以查找出来直接复制粘贴。当然你也可以试着查找一下你使用过的历史命令。

当然不是所有的程序都支持管道输入,支持管道输入的前提是能从 stdin 中读取数据。

重定向
重定向有两种方式输入重定向和输出重定向

输出重定向
先讲输出重定向是因为输出比输入更常用。

输出重定向有两种方式,覆盖 > 和在尾部追加 >> 具体使用方式看你的实际需求而定。

例如:

echo "hello" > hello.txt

我们将 echo 的输出重定向到 hello.txt 这个文件 现在我们可以打印它查看一下输出内容 cat hello.txt。不出意外的话应该会输出。

loli@Tokyo:~$ cat hello.txt 
hello
loli@Tokyo:~$ 

然后我们再来试一次 看一下我们的输出是不是被覆盖了。

echo "foober" > hello.txt

不出意外的话应该会输出

loli@Tokyo:~$ cat hello.txt
foober
loli@Tokyo:~

这里我们用我们的新输出覆盖了原来的输入,注意哦不是覆盖一行是用我们的新输出覆盖我们的整个文件。 如果你需要快速清空某个文件 可以使用 echo "" > file

然后另外一种方式就是追加了,追加顾名思义就是在已有内容的基础上进行添加。

比如:

echo "Hello World!" >> hello.txt

现在我们查看 hello.txt 的内容 cat hello.txt 下面是输出

loli@Tokyo:~$ echo "Hello World!" >> hello.txt
loli@Tokyo:~$ cat hello.txt
foober
Hello World!
loli@Tokyo:~$

追加方式重定向 会在文件的末尾新建一行并添加内容。

这里说一下我的个人使用感受,在我们编写脚本的过程中可能有很多数据需要处理,然后将这些处理都的数据供给下一个程序再次处理这种时候我一般会把处理过的数据放进一个文件下一个程序再进行读取。

输入重定向
输入重定向分为两种方式,从文件读取输入 < 和从终端读取输入 << 两种方式。具体看实际使用需求而定。

首先来讲从文件读取的方式吧。

比如我们以一个文件包含很多内容我们需要把它读取进我们的程序,我们可是使用输入重定向的方式。不过一般还是使用其他方式从文件中读数据不过这里讲到了 还是演示一下。

然后我们使用输出重定向从文件中读取内容。

cat < hello.txt

不出意外的话你的屏幕上会打印 Hello World! 我们上面的 while 循环的例子中就使用了这种方式将我们要处理的文件重重定向进循环进行处理。

另外一种就是从终端中读取内容并重定向的方式了,这种方式我感觉在写演示的时候用的比较多一点,因为这样我们就不用再终端和文件编辑直接换来换去了。

例如:

loli@Tokyo:~$ cat >> hello.txt <<EOF
> say
> hello
> EOF
loli@Tokyo:~$

这个例子中使用了输出重定向和输入重定向,先将输入重定向,<< 后面更了一个字符串 EOF 在最后也 有一个 EOF 字符串,其意思是 两个 EOF 之间的内容重定向到前面的 命令 cat (当然也可以是其他命令),字符串 EOF 你可以替换为其他字符串不过我个人建议使用 EOF 作为开始和结尾。然后将 cat 的输出追加到 hello.txt 文件,当然你也可以追加到任何你喜欢的文件。

管道
管道是我们将两个程序联系到一起的手段,说联系其实也不准确,准确的说是将一个程序的输出(stdout)作为另外一个程序的输入。管道输入的要求稍微苛刻一点,要管道前面的程序支持输出,这个比较简单,但是后面的程序支持从 stdin 中读取数据这个支持的比较少,看程序怎么设计的,比如前面例子中 使用的 grep 就支持从 stdin 中去取数据,简单的说就是将多个程序串起来,就像之前举的例子一样。

篇外

到这里不知道你发现没有,我们所学的知识一层跌一层,如果前面的知识没有了解,那么后面的操作就无法进行,其实大多的后端语言都是这种立体式的,正因为一层压一层我们才能更容易的在大脑里产生一个立体的概念。

我个人觉得 shell 语法是混乱的,怎么写都行,像一些现代的编程语言,写起来语法就比较爽了,简单直觉共通。在条件比对环节我稍微提了一下。shell 的比较操作符与其他语言的比较操作符的区别,是想告诉大家,这一部分并不通用。但是 shell依然有它的价值在适合的地方,可以比其它编程语言少写很多代码。

这篇教程只是引导你了解 shell 编程的最核心部分。在实际编写中有不懂的地方建议 google 一下。看看是目前的概念无法支撑你的程序,还是有其他的原因。如果你的程序中使用了很多概念是我没有讲的。那么我建议你可以学更高级的编程语言了,比如 python。因为再学习 shell 的收益,我个人觉得没有 python 高。关于 python 建议学 python最新版。Python2 对于初学者来说已经没有学习的价值了。

一开始我以为只有一点点,结果越写越多(逃