从零开始学习 Bash:学习足够的 Bash 知识来编写自己的脚本

2025-05-25

从零开始学习 Bash:学习足够的 Bash 知识来编写自己的脚本

你应该读一下@maxwell_dev 的帖子:我希望在此之前就了解的 Shell 简介

我发现自己总是需要编写 Shell 脚本,因为它们可以非常方便地自动执行任务。或者,最重要的是,因为我忘记了执行某些操作的完整命令,而我不想一直查找文档;要么是因为文档很烂,要么就是因为我太懒了 :)

有时我运行的命令很长,或者不太友好。例如,这个 Maven 命令:

mvn clean install -PautoInstallPackage,-codeQuality,-frontend -Dcrx.user=user1 -Dcrx.password=pass1
Enter fullscreen mode Exit fullscreen mode

这是不直观的,我想要一些更简单的东西,例如:

./install -codeQuality -frontend --user user1 --password pass1
Enter fullscreen mode Exit fullscreen mode

或更短:

./install -c -f -u user1 -p pass1

Enter fullscreen mode Exit fullscreen mode

看到这里你可能会想,这比实际的 maven 命令好在哪里?嗯:

  1. 我在install脚本中添加了文档。运行时我会打印文档。install help
  2. 我的脚本打印它们执行的完整命令。
  3. 命令本身是脚本的一部分,我总是可以在脚本内部找到它。

好了,现在就开始学习 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
Enter fullscreen mode Exit fullscreen mode

在同一目录中运行脚本:

./script
Enter fullscreen mode Exit fullscreen mode

./必要告诉你的终端查看当前目录而不是默认脚本目录。

现在用您最喜欢的编辑器打开该文件,让我们开始学习语法。

事情发生

TL;DR;#!/usr/bin/env bash在脚本文件的第一行添加以将该文件标记为 bash 可执行文件。

可以将其用于#!/usr/bin/env sh可移植性但是我们在这里专门使用 bash。

印刷品

要将内容打印到终端,您可以使用echoprintf

echo很简单,它会“回应”你向它抛出的内容:

例如script.sh,让我们从上面打印“hello world”

#!/usr/bin/env bash
echo hello world
Enter fullscreen mode Exit fullscreen mode

运行它:./script.sh它将打印:hello world

printf用于打印和“格式化”字符串,因此在最后。可以在这里f找到一些简单的例子 对我来说最大的用例是使用新行说明符打印新行
\n

例子:

#!/usr/bin/env bash
printf "hello\nworld\n"
Enter fullscreen mode Exit fullscreen mode

印刷:

hello
world
Enter fullscreen mode Exit fullscreen mode

评论

以 开头的任何行#都是注释。

声明变量

简单的:

MY_VARIABLE="my value"
Enter fullscreen mode Exit fullscreen mode

重要提示: 前后不能有空格=。Shell 使用空格作为命令参数的分隔符。

参数扩展

请参阅此处此处以了解更多信息。

引用或替换变量:

例如:

#!/usr/bin/env bash

FAV_FRUIT="apples"
echo FAV_FRUIT

# prints `FAV_FRUIT`
Enter fullscreen mode Exit fullscreen mode

尽管:

#!/usr/bin/env bash

FAV_FRUIT="apples"
echo $FAV_FRUIT

# prints `apples`
Enter fullscreen mode Exit fullscreen mode

你发现区别了吗?是的!FAV_FRUITvs $FAV_FRUIT

您还可以$在字符串中使用:

#!/usr/bin/env bash

FAV_FRUIT="apples"
I_LIKE_BLANK="I like $FAV_FRUIT"
echo $I_LIKE_BLANK

# prints `I like apples`
Enter fullscreen mode Exit fullscreen mode

注意:在上面的所有示例中,你可以替换$${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
Enter fullscreen mode Exit fullscreen mode

另一个例子:

#!/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`
Enter fullscreen mode Exit fullscreen mode

注意:注意到我们是如何${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"
Enter fullscreen mode Exit fullscreen mode

现在您FRUIT="Oranges"在运行之前声明./script

FRUIT="Oranges" ./script
Enter fullscreen mode Exit fullscreen mode

打印结果如下:I Like Oranges

因为默认值是Apricot

./script
Enter fullscreen mode Exit fullscreen mode

印刷:I Like Apricot

更高级的东西

If 语句

语法

如果那么
if [[ <some test> ]]; then
  <commands>
fi
Enter fullscreen mode Exit fullscreen mode
如果其他
if [[ <some test> ]]; then
  <commands>
else
  <other commands>
fi
Enter fullscreen mode Exit fullscreen mode
如果否则如果否则
if [[ <some test> ]]; then
  <commands>
elif [[ <some test> ]]; then
  <different commands>
else
  <other commands>
fi
Enter fullscreen mode Exit fullscreen mode

测试

上面的内容<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”

由于本文讨论的是bashShell,而非一般 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
Enter fullscreen mode Exit fullscreen mode

下表显示了上述脚本的命令和输出:

命令 输出
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

以下是一些有用的运算符

解析参数(whilecase语句)

这大概是最精彩的部分了。既然你已经学会了一些很酷的技巧,接下来我们来看看如何使用 API 传递参数。

本节的目标是创建一个打印用户信息的脚本。例如,以下命令:

./script --name Ahmed --height 6ft --occupation "Professional Procrastinator" --username coolestGuyEver23
Enter fullscreen mode Exit fullscreen mode

这将打印:

Hello, my name is Ahmed. I'm 6ft tall.
I work as a Professional Procrastinator and my username is coolestGuyEver23
Enter fullscreen mode Exit fullscreen mode

请注意,参数的顺序并不重要

让我们开始吧!

首先,我们需要一种方法来解析--name Ahmed --height 6ft --occupation= Developer --username coolestGuyEver23变量的参数。请参阅此 SO 答案

首先让我们看看 bash 的内置函数$#shift
$#返回传递的参数数量。例如:

#!/usr/bin/env bash

echo $#

Enter fullscreen mode Exit fullscreen mode

如果你运行./script arg1 arg2 arg3,输出将是3

shift

shift 命令是 Bash 自带的 Bourne Shell 内置命令之一。该命令接受一个参数,即数字。位置参数会向左移动该数字 N。从 N+1 到 $# 的位置参数会被重命名为从 $1 到 $# - N+1 的变量名。

用一个例子来更好地解释这一点:

#!/usr/bin/env bash

echo $#
shift 2
echo $#
shift
echo $#
echo $@
Enter fullscreen mode Exit fullscreen mode

运行./script arg1 arg2 arg3 arg4 arg5(总共 5 个参数)打印:

5
3
2
arg4 arg5
Enter fullscreen mode Exit fullscreen mode

嗯?所以shift 2基本上是先删除arg1arg2然后我们再次执行shift,再删除arg3注意,这$@是另一个打印参数的内置函数。

现在是时候教你如何像数组一样访问参数了。你可以使用$1来访问第一个参数,$2第二个参数,$3第三个参数……等等。

例子:

#!/usr/bin/env bash

echo $1
echo $3
echo $2
Enter fullscreen mode Exit fullscreen mode

跑步:

./script arg1 arg2 arg3
Enter fullscreen mode Exit fullscreen mode

印刷:

arg1
arg3
arg2
Enter fullscreen mode Exit fullscreen mode

我们如何使用这些信息?看看这个:

while [[ $# -gt 0 ]]; do
    key="$1"
    echo $key
    shift
done
Enter fullscreen mode Exit fullscreen mode

现在可能已经明白了!使用while上面的循环,加上退出条件[[ $# -gt 0 ]]shiftreduce,$#我们可以循环遍历所有传递的参数!

让我们用 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"

Enter fullscreen mode Exit fullscreen mode

笔记:

  1. 每个案例都用管道分隔,|例如-n|--name)匹配-n--name
  2. 我们用了shift两次来超越参数标志和参数值
  3. 有关case声明的更多信息
  4. 使用 找到第一个匹配项后,我们将退出;;。匹配未知选项位于底部。

发情:

./run.sh --name Ahmed --height 6ft
Enter fullscreen mode Exit fullscreen mode

或更短

./run.sh -n Ahmed -h 6ft
Enter fullscreen mode Exit fullscreen mode

印刷:

NAME: Ahmed
HEIGHT: 6ft
Enter fullscreen mode Exit fullscreen mode

把它们放在一起:

#!/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"
Enter fullscreen mode Exit fullscreen mode

并运行它:

./run.sh --name ahmed --height 6ft --occupation "professional procrastinator" --username coolestGuyEver23
Enter fullscreen mode Exit fullscreen mode

或者

./run.sh -n ahmed -h 6ft -o "professional procrastinator" -u coolestGuyEver23
Enter fullscreen mode Exit fullscreen mode

结果:

Hello, my name is ahmed. I'm 6ft tall.
I work as a professional procrastinator and my username is coolestGuyEver23
Enter fullscreen mode Exit fullscreen mode

注意,还记得上面的使用默认值部分吗?如果未传递参数,可以使用它来添加默认值。

评估命令

你可以将一个命令组装成字符串,然后用eval它来执行它。比如,还记得文章开头那个很长的 Maven 命令吗?

mvn clean install -PautoInstallPackage -Dcrx.user=user1 -Dcrx.password=pass1
Enter fullscreen mode Exit fullscreen mode

我们可以将其分解并组装起来:

假设我已经解析了PROFILEUSER并且PASSWORD

PROFILE="${PROFILE:-autoInstallPackage}"
USER="${USER:-user1}"
PASSWORD="${PASSWORD:-pass1}"

COMMAND="mvn clean install -P$PROFILE -Dcrx.user=$USER -Dcrx.password=$PASSWORD"

eval $COMMAND
Enter fullscreen mode Exit fullscreen mode

并且命令将被执行。

eval可能并非在所有情况下都是最佳选择。在这种情况下,它完全没问题,因为只有我一个人运行脚本,而不是最终用户。因此eval,在任何编程语言中使用 时都要谨慎。

上面的脚本可以不用如下方式重写eval

PROFILE="${PROFILE:-autoInstallPackage}"
USER="${USER:-user1}"
PASSWORD="${PASSWORD:-pass1}"
mvn clean install "-P$PROFILE" "-Dcrx.user=$USER" "-Dcrx.password=$PASSWORD" 
Enter fullscreen mode Exit fullscreen mode

谢谢@lietux

功能

函数简单明了。下面是一个打印传递给它的第一个参数的函数:

#!/usr/bin/env bash

printFirstOnly(){
  echo $1
}
# execute it
printFirstOnly hello world
Enter fullscreen mode Exit fullscreen mode

hello打印

函数参数通过变量访问,$N其中N是参数编号。

我喜欢使用函数来漂亮地打印东西:

pretty() {
  printf "***\n$1\n***\n"
}

pretty "hi there"
Enter fullscreen mode Exit fullscreen mode

印刷:

***
hi there
***
Enter fullscreen mode Exit fullscreen mode

但是,您可以将其用于任何目的。

结论

如果你能达到这个目标,那我肯定做对了 :) 我很想听听你对这篇文章的总体反馈。它条理清晰吗?还是比较零散?它讲得通吗?它有帮助吗?我该如何改进?

文章来源:https://dev.to/ahmedmusallam/bash-from-scratch-learn-enough-bash-to-write-your-own-scripts-189f
PREV
OOP 是一种软件开发群体精神病
NEXT
超棒的动画复选框 CSS 切换 - 日间/夜间模式