从零开始学习 Bash:学习足够的 Bash 知识来编写自己的脚本
你应该读一下@maxwell_dev 的帖子:我希望在此之前就了解的 Shell 简介
我发现自己总是需要编写 Shell 脚本,因为它们可以非常方便地自动执行任务。或者,最重要的是,因为我忘记了执行某些操作的完整命令,而我不想一直查找文档;要么是因为文档很烂,要么就是因为我太懒了 :)
有时我运行的命令很长,或者不太友好。例如,这个 Maven 命令:
mvn clean install -PautoInstallPackage,-codeQuality,-frontend -Dcrx.user=user1 -Dcrx.password=pass1
这是不直观的,我想要一些更简单的东西,例如:
./install -codeQuality -frontend --user user1 --password pass1
或更短:
./install -c -f -u user1 -p pass1
看到这里你可能会想,这比实际的 maven 命令好在哪里?嗯:
- 我在
install
脚本中添加了文档。运行时我会打印文档。install help
- 我的脚本打印它们执行的完整命令。
- 命令本身是脚本的一部分,我总是可以在脚本内部找到它。
好了,现在就开始学习 Shell 脚本吧!我会介绍一些让你快速上手编写脚本的小技巧!
基础知识
创建可执行文件
为了编写并执行脚本,脚本文件必须具有执行权限。因此,让我们创建一个文件并赋予它执行权限
cd
进入测试目录并运行
# create a new file called `script.sh`
touch script.sh
# add `execution` permission to the script to make it executable
chmod +x script.sh
在同一目录中运行脚本:
./script
有
./
必要告诉你的终端查看当前目录而不是默认脚本目录。
现在用您最喜欢的编辑器打开该文件,让我们开始学习语法。
事情发生!
TL;DR;#!/usr/bin/env bash
在脚本文件的第一行添加以将该文件标记为 bash 可执行文件。
您可以将其用于
#!/usr/bin/env sh
可移植性,但是我们在这里专门使用 bash。
印刷品
echo
很简单,它会“回应”你向它抛出的内容:
例如script.sh
,让我们从上面打印“hello world”
#!/usr/bin/env bash
echo hello world
运行它:./script.sh
它将打印:hello world
。
printf
用于打印和“格式化”字符串,因此在最后。可以在这里f
找到一些简单的例子 对我来说最大的用例是使用新行说明符打印新行。\n
例子:
#!/usr/bin/env bash
printf "hello\nworld\n"
印刷:
hello
world
评论
以 开头的任何行#
都是注释。
声明变量
简单的:
MY_VARIABLE="my value"
重要提示: 前后不能有空格
=
。Shell 使用空格作为命令参数的分隔符。
参数扩展
引用或替换变量:
例如:
#!/usr/bin/env bash
FAV_FRUIT="apples"
echo FAV_FRUIT
# prints `FAV_FRUIT`
尽管:
#!/usr/bin/env bash
FAV_FRUIT="apples"
echo $FAV_FRUIT
# prints `apples`
你发现区别了吗?是的!FAV_FRUIT
vs $FAV_FRUIT
。
您还可以$
在字符串中使用:
#!/usr/bin/env bash
FAV_FRUIT="apples"
I_LIKE_BLANK="I like $FAV_FRUIT"
echo $I_LIKE_BLANK
# prints `I like apples`
注意:在上面的所有示例中,你可以替换
$
为${variable name}
以获得相同的效果。因此$FAV_FRUIT
与${FAV_FRUIT}
第二点:注意到我们是如何
$FAV_FRUIT
在字符串内部使用的吗?这就是字符串模板!
使用默认值
如果变量为空或未定义,则使用某个默认值。最好用一个例子来说明:
#!/usr/bin/env bash
SHIRT_COLOR=""
COLOR="${SHIRT_COLOR:-red}"
echo $color
# prints: `red` since SHIRT_COLOR is empty
另一个例子:
#!/usr/bin/env bash
DEFAULT_COLOR="red"
SHIRT_COLOR=""
MY_SHIRT_COLOR="My shirt color is ${SHIRT_COLOR:-$DEFAULT_COLOR}"
echo $MY_SHIRT_COLOR
# prints: `My shirt color is red`
注意:注意到我们是如何
${SHIRT_COLOR:-$DEFAULT_COLOR}
在字符串内部使用的吗?这就是字符串模板!
从终端将变量传递给我们的脚本
要传递变量,可以在运行脚本之前声明它。
这对于传递环境变量尤其有用。例如:以下脚本需要 a FRUIT
,并将打印I Like <FRUIT>
。如果FRUIT
未定义,请使用Apricot
。
#!/usr/bin/env bash
DEFAULT_FRUIT="Apricot"
FRUIT=${FRUIT:-$DEFAULT_FRUIT}
echo "I Like $FRUIT"
现在您FRUIT="Oranges"
在运行之前声明./script
:
FRUIT="Oranges" ./script
打印结果如下:I Like Oranges
因为默认值是Apricot
:
./script
印刷:I Like Apricot
更高级的东西
If 语句
语法
如果那么
if [[ <some test> ]]; then
<commands>
fi
如果其他
if [[ <some test> ]]; then
<commands>
else
<other commands>
fi
如果否则如果否则
if [[ <some test> ]]; then
<commands>
elif [[ <some test> ]]; then
<different commands>
else
<other commands>
fi
测试
上面的内容<some test>
可以用测试条件替换。你可以在终端中test
输入以下代码来查看所有可用条件:man test
以下是一些评估结果为真的示例条件:
测试 | 描述 |
---|---|
[[2 -gt 1]] |
2 大于 1。对应词-lt :小于。 |
[[2 -eq 2]] |
2等于2 |
[[3 -ge 3]] |
3 大于或等于 3。对应项-le :小于或等于 |
[[-n "hello"]] |
“hello”的长度大于0 |
[[-z ""]] |
“”的长度为0 |
[["apple"= "apple"]] |
字符串,“apple”等于字符串“apple”。(-eq 比较数字,而= 比较字符) |
[["apple"!= "apple1"]] |
字符串“apple”不等于字符串“apple1” |
使用通配符的示例:
- [[ "watermelon" = *"melon"* ]]
:字符串“watermelon”包含“melon”
由于本文讨论的是
bash
Shell,而非一般 Shell,因此我们使用 Bash 的双方括号[[]]
。更多信息,请阅读这篇文章和这个答案。
本文是进一步测试条件的良好资源
为了继续体现示例和成果的精神,这里有一个例子:
#!/usr/bin/env bash
if [[ ${#FRUIT} -gt 4 ]] # FRUIT character count is greater than 4
then
echo "[$FRUIT]: yay! your fruit has more than 4 characters!"
elif [[ ${#FRUIT} -lt 4 ]] # FRUIT character count is less than 4
then
echo "[$FRUIT]: Unbelievable... your fruit has less than 4 characters..."
else # FRUIT character count must be 4
echo "A fruit with exactly 4 characters, how precious!"
fi
下表显示了上述脚本的命令和输出:
命令 | 输出 |
---|---|
FRUIT="Apple" ./script.sh |
[Apple]: yay! your fruit has more than 4 characters! |
FRUIT="Fig" ./script.sh |
[Fig]: Unbelievable... your fruit has less than 4 characters... |
FRUIT="Pear" ./script.sh |
A fruit with exactly 4 characters, how precious! |
注意参数扩展
${#FRUIT}
获取字符长度FRUIT
以下是一些有用的运算符
解析参数(while
和case
语句)
这大概是最精彩的部分了。既然你已经学会了一些很酷的技巧,接下来我们来看看如何使用 API 传递参数。
本节的目标是创建一个打印用户信息的脚本。例如,以下命令:
./script --name Ahmed --height 6ft --occupation "Professional Procrastinator" --username coolestGuyEver23
这将打印:
Hello, my name is Ahmed. I'm 6ft tall.
I work as a Professional Procrastinator and my username is coolestGuyEver23
请注意,参数的顺序并不重要
让我们开始吧!
首先,我们需要一种方法来解析--name Ahmed --height 6ft --occupation= Developer --username coolestGuyEver23
变量的参数。请参阅此 SO 答案
首先让我们看看 bash 的内置函数$#
并shift
$#
返回传递的参数数量。例如:
#!/usr/bin/env bash
echo $#
如果你运行./script arg1 arg2 arg3
,输出将是3
shift 命令是 Bash 自带的 Bourne Shell 内置命令之一。该命令接受一个参数,即数字。位置参数会向左移动该数字 N。从 N+1 到 $# 的位置参数会被重命名为从 $1 到 $# - N+1 的变量名。
用一个例子来更好地解释这一点:
#!/usr/bin/env bash
echo $#
shift 2
echo $#
shift
echo $#
echo $@
运行./script arg1 arg2 arg3 arg4 arg5
(总共 5 个参数)打印:
5
3
2
arg4 arg5
嗯?所以shift 2
基本上是先删除arg1
,arg2
然后我们再次执行shift
,再删除arg3
。注意,这$@
是另一个打印参数的内置函数。
现在是时候教你如何像数组一样访问参数了。你可以使用$1
来访问第一个参数,$2
第二个参数,$3
第三个参数……等等。
例子:
#!/usr/bin/env bash
echo $1
echo $3
echo $2
跑步:
./script arg1 arg2 arg3
印刷:
arg1
arg3
arg2
我们如何使用这些信息?看看这个:
while [[ $# -gt 0 ]]; do
key="$1"
echo $key
shift
done
现在可能已经明白了!使用while
上面的循环,加上退出条件[[ $# -gt 0 ]]
和shift
reduce,$#
我们可以循环遍历所有传递的参数!
让我们用 case 语句让它变得更好,让我们从简单开始,然后解析,name
然后height
#!/usr/bin/env bash
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
# parse name arg
-n|--name)
NAME="$2" # the value is right after the key
shift # past argument (`--name`)
shift # past value
;;
# parse height arg
-h|--height)
HEIGHT="$2"
shift # past argument
shift # past value
;;
# unknown option
*)
echo "Unknown argument: $key"
shift # past argument
;;
esac
done
echo "NAME: $NAME"
echo "HEIGHT: $HEIGHT"
笔记:
- 每个案例都用管道分隔,
|
例如-n|--name)
匹配-n
或--name
- 我们用了
shift
两次来超越参数标志和参数值- 有关
case
声明的更多信息- 使用 找到第一个匹配项后,我们将退出
;;
。匹配未知选项位于底部。
发情:
./run.sh --name Ahmed --height 6ft
或更短
./run.sh -n Ahmed -h 6ft
印刷:
NAME: Ahmed
HEIGHT: 6ft
把它们放在一起:
#!/usr/bin/env bash
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
# parse name arg
-n|--name)
NAME="$2" # the value is right after the key
shift # past argument (`--name`)
shift # past value
;;
# parse height arg
-h|--height)
HEIGHT="$2"
shift # past argument
shift # past value
;;
# parse user arg
-u|--user)
USER="$2"
shift # past argument
shift # past value
;;
# parse occupation argument
-o|--occupation)
OCCUPATION="$2"
shift # past argument
shift # past value
;;
# parse code quality argument
-u|--username)
USERNAME="$2"
shift # past argument
shift # past value
;;
# unknown option
*)
echo "Unknown argument: $key"
shift # past argument
;;
esac
done
echo "Hello, my name is $NAME. I'm $HEIGHT tall.
I work as a $OCCUPATION and my username is $USERNAME"
并运行它:
./run.sh --name ahmed --height 6ft --occupation "professional procrastinator" --username coolestGuyEver23
或者
./run.sh -n ahmed -h 6ft -o "professional procrastinator" -u coolestGuyEver23
结果:
Hello, my name is ahmed. I'm 6ft tall.
I work as a professional procrastinator and my username is coolestGuyEver23
注意,还记得上面的使用默认值部分吗?如果未传递参数,可以使用它来添加默认值。
评估命令
你可以将一个命令组装成字符串,然后用eval
它来执行它。比如,还记得文章开头那个很长的 Maven 命令吗?
mvn clean install -PautoInstallPackage -Dcrx.user=user1 -Dcrx.password=pass1
我们可以将其分解并组装起来:
假设我已经解析了
PROFILE
,USER
并且PASSWORD
PROFILE="${PROFILE:-autoInstallPackage}"
USER="${USER:-user1}"
PASSWORD="${PASSWORD:-pass1}"
COMMAND="mvn clean install -P$PROFILE -Dcrx.user=$USER -Dcrx.password=$PASSWORD"
eval $COMMAND
并且命令将被执行。
eval
可能并非在所有情况下都是最佳选择。在这种情况下,它完全没问题,因为只有我一个人运行脚本,而不是最终用户。因此eval
,在任何编程语言中使用 时都要谨慎。
上面的脚本可以不用如下方式重写eval
:
PROFILE="${PROFILE:-autoInstallPackage}"
USER="${USER:-user1}"
PASSWORD="${PASSWORD:-pass1}"
mvn clean install "-P$PROFILE" "-Dcrx.user=$USER" "-Dcrx.password=$PASSWORD"
谢谢@lietux
功能
函数简单明了。下面是一个打印传递给它的第一个参数的函数:
#!/usr/bin/env bash
printFirstOnly(){
echo $1
}
# execute it
printFirstOnly hello world
hello
仅打印
函数参数通过变量访问,
$N
其中N
是参数编号。
我喜欢使用函数来漂亮地打印东西:
pretty() {
printf "***\n$1\n***\n"
}
pretty "hi there"
印刷:
***
hi there
***
但是,您可以将其用于任何目的。
结论
如果你能达到这个目标,那我肯定做对了 :) 我很想听听你对这篇文章的总体反馈。它条理清晰吗?还是比较零散?它讲得通吗?它有帮助吗?我该如何改进?
文章来源:https://dev.to/ahmedmusallam/bash-from-scratch-learn-enough-bash-to-write-your-own-scripts-189f