Skip to content

Latest commit

 

History

History
385 lines (284 loc) · 9.46 KB

05.Shell脚本入门.md

File metadata and controls

385 lines (284 loc) · 9.46 KB

Shell 脚本入门

使用 shell 脚本,可以在某些场合重复使用,提高利用率。

  • 脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。一般称为 Shebang
#!/bin/sh
  • 如果它没有放在 bin 目录下,就无法执行,为了保险起见,使用以下方式执行
#!/usr/bin/env sh
  • 如果不指定 shebang,那么就需要指定脚本解释器 bash ...

执行权限

关于脚本执行的前提条件就是,它需要获得这个权限

# 给所有用户执行权限
➜ chmod +x script.sh

# 给所有用户读权限和执行权限,拥有者有所有权限
➜ chmod +rx script.sh
# 或者
➜ chmod 755 script.sh

# 只给脚本拥有者读权限和执行权限
➜ chmod u+rx script.sh
# 或者
➜ chmod 700 script.sh

脚本调用时,一般需要指定脚本路径,如果我们将其加入 $PATH (环境变量)中,就不需要指定路径

export PATH=$PATH:~/bin
  • 上面命令改变环境变量 $PATH,将 ~/bin 添加到 $PATH 的末尾。在 bin 下的所有脚本文件可以直接执行,而不需要输入路径

  • 可以将这一行加到 ~/.bashrc 文件里面,然后重新加载一次 .bashrc,这个配置就可以生效了。

    • 如果是 zsh,那么需要在 .zshrc 中配置
    • 然后使用 source 重新启动配置
    source ~/.bashrc

env

env 命令总是指向 /usr/bin/env 文件,此文件总是在 /usr/bin 目录下

  • 下面语法的意思就是让 shell 咋着 $PATH 环境变量中第一个匹配的 node(name)。
#!/usr/bin/env node
  • env 命令的参数
    • -i 或 --ignore-environment:不带环境变量启动
    • -u, --unset=NAME:从环境变量中删除一个变量

脚本参数

  • $0:脚本文件名,即 script.sh
  • $1~$9:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量 $IFS 值的第一个字符分隔,默认为空格,但是可以自定义
#!/usr/bin/env bash

echo "all the argumengs" $@
echo "all the argumengs**" $*
echo "script name" $0
#all the argumengs -o for bar
#all the argumengs** -o for bar
#script name ./script.sh
  • 我们也可以使用 for 循环打印参数。如果传入的参数是在 "" 中的,那么会视为一个整体,如 "a b" -> a b
for i in "$@"; do
  echo $i
done

shift 命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1),这使后main的参数会向前移一位,如 $2 -> $1 ...

echo "一共输入了 $# 个参数"

while [ "$1" != "" ]; do
  echo "剩下 $# 个参数"
  echo "参数:$1"
  shift
done
  • 如果指定整数,则是当前要移除的参数个数
# $3 -> $1
shift 2

getopts

getopts 命令拥于脚本内部,解析复杂的脚本命令行参数。通常与 while 循环一起使用,取出脚本所有的带有前置连词线(-)的参数

getopts optstring name
  • getopts 接受两个参数,第一个参数表示脚本的所有的连字符参数。例如,某个脚本可以有三个配置项参数 -l-h-a,其中只有 -a 可以带有参数值,而-l和-h是开关参数,那么getopts的第一个参数写成 lha:,顺序不重要。
    • a 后面带有 :,表示 -a 后带有参数值。getopts 的第二个参数 name 是一个变量名,用来保存当前取到的配置项参数
while getopts 'lha:' OPTION; do
  case "$OPTION" in
    l)
      echo "linuxconfig"
      ;;

    h)
      echo "h stands for h"
      ;;

    a)
      avalue="$OPTARG"
      echo "The value provided is $OPTARG"
      ;;
    ?)
      echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
      exit 1
      ;;
  esac
done
shift "$(($OPTIND - 1))"
  • ?: 指用户输入了没有指定的参数,例如 -x
  • $OPTARG: 指的是保存的参数值,如 -a foo,那么 $OPTARG 就是 foo
    • 注意:只要遇到不带连词线的参数,那么 getopts 就会失败
    • 可以将多个连词写到一起,比如 ./script.sh -la fw,但是不能写成 -al,如果不带有参数,位置可以互换
  • $OPTIND: 在 getopts 开始执行前是 1,然后每次执行就会加 1(在处理 -a 之后的参数是也会加 1)
    • 在 while 循环结束后,$OPTIND - 1 就是已经处理的连词线参数个数,使用 shift 命令将这些参数移除。

配置项参数终止符(--)

--- 开头的参数,会被 Bash 当作配置项解释。但是有时候,它们就是实体的一部分

# 需要使用 -- 读取 --file 文件
cat --file

cat -- --file
  • 如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符 --
myPath="-l"
ls -- $myPath
#ls: 无法访问'-l': 没有那个文件或目录
  • 使用正则查找的时候也可以使用
grep -- "--hello" example.txt

exit

用于退出当前的脚本,并向终端输出一个退出值

  • exit命令后面可以跟参数,该参数就是退出状态。
    • 0 表示正常,1 表示发生错误,2 表示用法不对。
    • 126 表示不是可执行脚本,127 表示命令没有发现。如果脚本被信号 N 终止,则退出值为 128 + N。
if [ $(id -u) != "0" ]; then
  echo "根用户才能执行当前脚本"
  exit 1
fi
  • id -u 返回用户的 ID,一旦用户的 ID 不等于 0(即不是 root),就会失败退出

每个命令行执行结束后都会返回一个结果,如果成功,即是 0

cd /path/to/somewhere
# $? 指的是上一个命令行返回的结果,
# 即,如果有 /path/to/somewhere 这个目录,则返回 0
if [ "$?" = "0" ]; then
  rm *
else
  echo "无法切换目录!" 1>&2
  exit 1
fi
  • 也可以使用更简洁的方式去写
cd /path/to/somewhere && rm *

cd /path/to/somewhere || exit 1

source

source 命令用于执行一个脚本,通常用于重新加载一个配置文件

source .bashrc
  • source 命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell
  • 例如,先创建一个 test 脚本
#!/bin/bash
# test.sh
echo $foo
  • 然后打印输出变量。当前 Shell 的变量 foo 并没有 export,所以直接执行无法读取,但是 source 执行可以读取
# 当前 Shell 新建一个变量 foo
$ foo=1
# 打印输出 1
$ source test.sh
1
# 打印输出空字符串
$ bash test.sh

当使用 shell 引用外部库的时候,我们可以直接使用内部的函数

#!/bin/bash

source ./lib.sh

function_from_lib
  • source 还有一个简写形式(.
. .bashrc

alias

  • alias: 显示所有别名
  • alias NAME=DEFINITION:为原始命令设置别名
  • unalias NAME: 解除别名

read

read [-options] [var...]

read 命令用于将用户的输入存入一个变量

#!/bin/bash

echo -n "输入>>"
read text
echo "你的输入:$text"
  • 你也可以同时定义多个变量,但是用户输入时不同变量之间也需要使用空格隔开才可以
echo -n "输入>>"
read text text2
echo "你的输入:$text $text2"
  • 如果 read 后没有设置变量,那么用户输入的所有变量都会被环境变量 REPLY 包含
#!/bin/bash

echo -n "输入>>"
read
echo "你的输入:$REPLY"
  • 使用 read 也可以读取文件,done 之后的是定向符(<),将文件的内容导向 read 命令,每次读取一行,存入变量 muline
#!/bin/bash

filename='/etc/hosts'
while read myline
do
  echo "$myline"
done < $filename

options

  1. -t: 指定如果超时多少秒,自动关闭

  2. -p: 指定用户输入的信息

    read -p "输入一个或多个值>>"
    echo "$REPLY"
  3. -a: 把用户输入赋值给一个数组,从零号位置开始

    read -a test
    # a b c
    echo ${people[2]}
    # c
  4. -n: 指定只读取若干个字符作为变量值

    read -n 3 letter
    # abcdef
    echo $letter
    # abc
  5. -e: 该参数允许用户输入的时候可以使用 tab 补全

  6. -r: raw 模式,表示不把用户输入的 \ 作为转义字符解释

  7. -s: 使用户的输入不现实屏幕上,用于输入密码或保密信息

  8. -u fd: 使用文件描述符 fd 作为输入

  9. -d limit: 定义字符串 limit 的第一个字符作为用户输入的结束,而不是一个换行符

    #!/bin/bash
    # 如果用户输入 f 就会结束,而不是换行符
    echo -n "输入>>"
    read -d fw
    echo "你的输入:$REPLY"

IFS

read 命令读取的值,默认是以空格分隔。可以通过自定义环境变量 IFS,修改分隔符

  • 如果把IFS定义成冒号(:)或分号(;),就可以分隔以这两个符号分隔的值,这对读取文件很有用。
#!/bin/bash
# read-ifs: read fields from a file

FILE=/etc/passwd

read -p "Enter a username > " user_name
file_info="$(grep "^$user_name:" $FILE)"

if [ -n "$file_info" ]; then
  IFS=":" read user pw uid gid name home shell <<< "$file_info"
  echo "User = '$user'"
  echo "UID = '$uid'"
  echo "GID = '$gid'"
  echo "Full Name = '$name'"
  echo "Home Dir. = '$home'"
  echo "Shell = '$shell'"
else
  echo "No such user '$user_name'" >&2
  exit 1
fi