shell最全笔记

文章目录

一. linux中的shell

1、查看linux使用的shell

在linux中多个shell,有c shell, k shell,等等。在centos7中,Bourne Again shell才是主要使用的,即是bash.

  • 查看/etc/shells

    lrwxrwxrwx. 1 1M Dec 10 08:43     /bin/sh -> bash
    -rwxr-xr-x. 1 1M Mar 31  2020     /bin/bash
    lrwxrwxrwx. 1 1M Dec 10 08:43     /usr/bin/sh -> bash
    -rwxr-xr-x. 1 1M Mar 31  2020     /usr/bin/bash
    

    可以看出都是指向的/bin/bash和/usr/bin/bash

    [root@localhost ~]# md5sum /bin/bash  /usr/bin/bash
    708c8760385810080c4d17fa84d325ca  /bin/bash
    708c8760385810080c4d17fa84d325ca  /usr/bin/bash
    

    两个文件内容是一样的。

2. 设置命令别名alias
[root@localhost ~]# cat .bashrc
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi
[root@localhost ~]#

在这个文件添加一个alias别名,然后source 文件名

3. 查询命令是否内置

使用type命令查看一个linux,命令是否是内置命令

[dmtsai@study ~]$ type [-tpa] name
选项与参数:    
   :不加任何选项与参数时,type 会显示出 name 是外部指令还是 bash 内置指令
-t :当加入 -t 参数时,type 会将 name 以下面这些字眼显示出他的意义:
    file :表示为外部指令;
    alias :表示该指令为命令别名所设置的名称;
    builtin :表示该指令为 bash 内置的指令功能;
-p :如果后面接的 name 为外部指令时,才会显示完整文件名;
-a :会由 PATH 变量定义的路径中,将所有含 name 的指令都列出来,包含 alias

[dmtsai@study ~]$ type cd
cd is a shell builtin # ==看到了吗? cd 是 shell 内置指令
4. linux快捷键
快捷键作用
ctrl+a将光标快速移动到行首
ctrl+e讲光标快速移动到行尾
ctrl+ 方向键左或右将光标按照单词快速左右移动
ctrl+w将空格分割的一个字符串整体进行删除。剪切
ctrl+u将光标所有位置到行首内容进行删除(剪切)
ctrl+k将光标所在位置到行尾内容进行删除(剪切)
ctrl+y粘贴剪切内容
ctrl+r查找历史执行的命令
5. 环境变量的功能

环境变量可以帮我们达到很多功能~包括主文件夹的变换啊、提示字符的显示啊、可执行文
件搜寻的路径啊等等的

  • 默认的环境变量 - env查看shell

    root@localhost ~]# env | grep -v "LS_COL"
    XDG_SESSION_ID=5
    HOSTNAME=localhost.localdomain
    SELINUX_ROLE_REQUESTED=
    TERM=xterm
    SHELL=/bin/bash
    HISTSIZE=1000
    SSH_CLIENT=192.168.152.1 63257 22
    SELINUX_USE_CURRENT_RANGE=
    SSH_TTY=/dev/pts/0
    USER=root
    MAIL=/var/spool/mail/root
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
    PWD=/root
    LANG=en_US.UTF-8
    MODULEPATH=/usr/share/Modules/modulefiles:/etc/modulefiles
    LOADEDMODULES=
    SELINUX_LEVEL_REQUESTED=
    HISTCONTROL=ignoredups
    SHLVL=1
    HOME=/root
    LOGNAME=root
    SSH_CONNECTION=192.168.152.1 63257 192.168.152.101 22
    MODULESHOME=/usr/share/Modules
    LESSOPEN=||/usr/bin/lesspipe.sh %s
    XDG_RUNTIME_DIR=/run/user/0
    BASH_FUNC_module()=() {  eval `/usr/bin/modulecmd bash $*`
    }
    _=/usr/bin/env
    OLDPWD=/root
    

env 是 environment (环境) 的简写,是列出来所有的环境变量。

6. bash的操作环境
/etc/profile
~/.bash_profile
开始操作bash
/etc/profile.d/*.sh
/etc/locale.conf
~/.bashrc
/etc/bashrc

虚线表示存在这个文件,就先从虚线走。

可以看~/.bash_profile就知道

[root@localhost ~]# cat ~/.bash_profile
# .bash_profile

# 存在 ~/.bashrc就加在.bashrc
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH

本质来说,bash登陆系统后,系统只会读取/etc/profile,这个是所有用户都会有的设置。

然后/etc/profile这个脚本里面调用了其他设置环境的脚步。

7. 通配符和特殊字符

通配符

符号意义
*代表“ 0 个到无穷多个”任意字符
?代表“一定有一个”的任意字符
[ ]同样代表“一定有一个在括号内”的字符(非任意字符)。例如 [abcd] 代表“一定有一个字符, 可能是 a, b, c, d 这四个任何一个”
[-]若有减号在中括号内时,代表“在编码顺序内的所有字符”。例如 [0-9] 代表 0 到 9 之间的所有数字,因为数字的语系编码是连续的!
[^]若中括号内的第一个字符为指数符号 (^) ,那表示“反向选择”,例如 [^abc] 代表一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。
{string1,string2,…}匹配 sring1 或 string2 (或更多)其一字符串;a{abc,xyz,123}b 列出aabcb,axyzb,a123b

特殊字符

符号含义
#注释
\转义符号:将“特殊字符或通配符”还原成一般字符
``
;连续指令下达分隔符号
~使用者的家目录
$取用变量前置字符:亦即是变量之前需要加的变量取代值
&工作控制 (job control):将指令变成背景下工作
!逻辑运算意义上的“非” not 的意思!
/目录符号:路径分隔的符号
>, >>数据流重导向:输出导向,分别是“取代”与“累加”
<, <<数据流重导向:输入导向
’ ’单引号,不具有变量置换的功能
" "具有变量置换的功能!
`两个“ ` ”中间为可以先执行的指令
( )在中间为子 shell 的起始与结束
{ }在中间为命令区块的组合!
8. 数据重定向
标准输入<,<<
标准输出>,>>
标准错误输出2>,2>>
file
命令
屏幕或文件或设备
屏幕或文件或设备
[root@localhost ~]# ls -l /dev/stdin /dev/stdout /dev/stderr
lrwxrwxrwx. 1 root root 15 Apr  9 03:02 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root 15 Apr  9 03:02 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root 15 Apr  9 03:02 /dev/stdout -> /proc/self/fd/1
[root@localhost ~]# ls -l /proc/self/fd/{0,1,2}
lrwx------. 1 root root 64 Apr  9 11:59 /proc/self/fd/0 -> /dev/pts/0
lrwx------. 1 root root 64 Apr  9 11:59 /proc/self/fd/1 -> /dev/pts/0
lrwx------. 1 root root 64 Apr  9 11:59 /proc/self/fd/2 -> /dev/pts/0

默认的都是指向/dev/pts/0,就是我们的xshell终端。

名称代码操作符
标准输入(stdin)0<, <<
标准输出(stdout)1>或1>, >>或1>>
标准错误输出(stderr)22>, 2>>
  • 1> :以覆盖的方法将“正确的数据”输出到指定的文件或设备上;

  • 1>>:以累加的方法将“正确的数据”输出到指定的文件或设备上;

  • 2> :以覆盖的方法将“错误的数据”输出到指定的文件或设备上;

  • 2>>:以累加的方法将“错误的数据”输出到指定的文件或设备上;

  • 案例一: 将错误数据丢弃,项目显示正确数据;

    [root@localhost ~]# find / -name .bashrc
    find: ‘/proc/15215’: No such file or directory #这一行就是错误信息
    /etc/skel/.bashrc
    /root/.bash.bashrc
    /home/joker/.bashrc
    
    [root@localhost ~]# find / -name .bashrc 2>/dev/null #/dev/null就是垃圾桶
    /etc/skel/.bashrc
    /root/.bashrc
    /home/joker/.bashrc
    
  • 案例二: 将错误和正确的分别输出到不同文件。

    [root@localhost ~]# cat error.log
    Traceback (most recent call last):
      File "test.py", line 4, in <module>
        raise defineError  # 主动抛出异常
    Exception: 搞个异常
    [root@localhost ~]# cat correct.log
    test
    [root@localhost ~]# cat test.py
    #!/usr/bin/python3
    print("test")
    defineError = Exception("搞个异常")
    raise defineError  # 主动抛出异常
    
  • 案例三: 将错误和正确的都输出到同一文件

    [root@localhost ~]# python3 test.py >& all.log
    [root@localhost ~]# cat all.log
    test
    Traceback (most recent call last):
      File "test.py", line 4, in <module>
        raise defineError  # 主动抛出异常
    Exception: 搞个异常
    
    #等同如下
    [root@localhost ~]# python3 test.py  >all.log 2>&1 #2>&1的位置必须在最后。
    [root@localhost ~]# cat test.py
    #!/usr/bin/python3
    print("test")
    defineError = Exception("搞个异常")
    
  • cat和标准输入

    stdinput和<和<<: 就是将原本需要由键盘输入的数据,改由文件内容来取代。

    • 键盘输入实例:

      [root@localhost ~]# cat > catfile
      line 1
      line 2
      line 3 #键盘输入到最后一行时,使用ctrl+d离开。
      [root@localhost ~]# cat catfile
      line 1
      line 2
      line 3
      
    • 文件输入取代键盘输入

      [root@localhost ~]# cat log.txt
      wokao
      [root@localhost ~]# cat >output.log <log.txt
      [root@localhost ~]# cat output.log
      wokao
      [root@localhost ~]#
      
    • 使用键盘输入+结束符

      [root@localhost ~]# cat <<EOF >output.log  #EOF不是特殊字符,可以换成任意,只要配对就行。
      > line1
      > line2
      > EOF
      [root@localhost ~]# cat output.log
      line1
      line2
      
  • 在while循环中使用read

    [root@localhost ~]# sh run.sh
    1
    21
    321
    4321
    [root@localhost ~]# cat run.sh
    #!/bin/bash
    while read -r line
    do
            echo $line
    done < log.txt
    
9. 命令执行的判断依据: ;,&&,||
  • 分号(;):表示命令之间都是独立的。

    [root@localhost ~]# asg;ls
    

    asg即便报错,它不是系统中的命令,那么也会执行第二个命令ls

  • $? (指令回传值) 与 && 或 ||

    如果根据上一次的执行正确与否,来判断是否执行下一个指令。这就必须借住$?了。

    指令下达情况说明
    cmd1&&cmd21. 若 cmd1 执行完毕且正确执行( ? = 0 ),则开始执行 c m d 2 。 2. 若 c m d 1 执行完毕且为错误( ?=0),则开始执行 cmd2。 2. 若 cmd1执行完毕且为错误 ( ?=0),则开始执行cmd22.cmd1执行完毕且为错误(?≠0),则 cmd2 不执行。
    cmd1 ||cmd21. 若 cmd1 执行完毕且正确执行( ? = 0 ),则 c m d 2 不执行。 2. 若 c m d 1 执行完毕且为错误( ?=0),则 cmd2 不执行。 2. 若 cmd1 执行完毕且为错误 ( ?=0),则cmd2不执行。2.cmd1执行完毕且为错误(?≠0),则开始执行 cmd2。
    范例一:使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 创建 /tmp/abc/hehe
    [dmtsai@study ~]$ ls /tmp/abc && touch /tmp/abc/hehe
    ls: cannot access /tmp/abc: No such file or directory
    # ls 很干脆的说明找不到该目录,但并没有 touch 的错误,表示 touch 并没有执行
    
    范例二:测试 /tmp/abc 是否存在,若不存在则予以创建,若存在就不作任何事情
    [dmtsai@study ~]$ ls /tmp/abc || mkdir /tmp/abc
    
    范例三:我不清楚 /tmp/abc 是否存在,但就是要创建 /tmp/abc/hehe 文件
    [dmtsai@study ~]$ ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
    
10. pipe管道

这个管线命令“ | ”仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的信息,对于 stdandard error 并没有直接处理的能力。

[root@localhost ~]# ls /etc/ | head -n 5
adjtime
aliases
aliases.db
alternatives
anacrontab

前一个命令标准输出 作为后一个命令的标准输入。

11. tee双向重导向
标准输入
tee
屏幕
文件

tee 会同时将数据流分送到文件去与屏幕 (screen);而输出到屏幕的,其实就是 stdout ,那就可以让下个指令继续处理喔!

[dmtsai@study ~]$ tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!
[dmtsai@study ~]$ last | tee last.list | cut -d " " -f1
# 这个范例可以让我们将 last 的输出存一份到 last.list 文件中;
[dmtsai@study ~]$ ls -l /home &#124; tee ~/homefile &#124; more
# 这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出讯息!
[dmtsai@study ~]$ ls -l / |  tee -a ~/homefile | more
# 要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将讯息累加。
12. 后台运行命令

nohup能让运行的命令或脚步在你退出系统后,继续在后台运行。

$ nohup cmd arg1...

但是nohup命令不能自动将放在后台运行,必须配合&使用

[root@localhost ~]# nohup sleep 10   &> log.txt & #最后一个&才是。&>是重定向
[5] 51372
[4]   Done                    nohup sleep 10
13. 结束程序
$ exit CODEVALUE

实例如下:

[root@localhost ~]# cat digit.sh
#!/bin/bash
INPUT=$1
if [[ $INPUT =~ ^[0-9]+$ ]];then
        echo "$INPUT is a digit"
        exit 0
else
        echo "Not a digit"
        exit 1
fi
14. 文件描述符fd和重定向

我们一般使用文件描述符(fd)的范围为0-9,重定向时大于9的文件描述符要谨慎使用,因为可能与shell内部的使用的文件描述符冲突。

有些文件描述符是在shell启动时就自动被建立的,如标准输入0,标准输出1,标准错误输出2;

每个程序都在/proc有个目录,fd在/proc/$mypid/fd

1. 使用exec命令

exec和fd谨慎使用。

bash的内部命令exec的功能之一就是允许我们操作文件描述符。如果在exec之后没有指定命令,则exec命令之后的重定向将更改当前shell的文件描述符。

  • exec有下面几种格式:

    • exec < file
    • exec [n]<& file
    • exec [n]<& [3..9]
    • exec > file
    • exec [n] > file
    • exec [n] >& [3..9] 这里的>&和前面第8小节将讲的并不一样。
    • exec [n]<>file 读和写的文件描述符
  • 在脚本中,将命令产生的错误输出到文件

    exec 2> errors.log
    
  • 使用exec解决重定向被继承导致的错误。

    [root@localhost ~]# sh run.sh
    1
    1
    21
    321
    [root@localhost ~]# cat run.sh
    #!/bin/bash
    while read -r line
    do
            echo $line
            read -p "press any key" -n 1
    done < log.txt
    [root@localhost ~]#
    

    因为我们将指定的文件重定向到了whiel循环的标准输入中(文件描述符0),即我们指定的文件将被打开以用于标准输入的读取,而循环中的所有命令包括read命令都会继承这个文件描述符(这里是0),因此里面的read -p读取不到我们的键盘输入了。

    **解决方法:**使用exec进行指定文件描述符

    [root@localhost ~]# cat run.sh
    #!/bin/bash
    exec  3< log.txt #将log.txt输入到文件描述符3
    while read -u 3 line #-u从文件描述符3读取。
    do
            echo $line
            read -p "press any key" -n 1
    done
    exec 3<&- #关闭文件描述符。
    #上次操作类似我们自己fileHandle=open(file),close(fileHandle)
    [root@localhost ~]# sh -x run.sh
    + exec
    + read -u 3 line
    + echo 1
    1
    + read -p 'press any key' -n 1
    press any key1+ read -u 3 line
    + echo 21
    21
    + read -p 'press any key' -n 1
    press any key1+ read -u 3 line
    + echo 321
    321
    + read -p 'press any key' -n 1
    press any key1+ read -u 3 line
    + echo 4321
    4321
    + read -p 'press any key' -n 1
    press any key1+ read -u 3 line
    + exec
    

    上面read -p就可以从标准输入1中读取了。

  • 使用exec给一个输入文件或输出文件指定一个文件描述符(类似文件句柄)

    exec [n]< file #n是可选的,不写的话,那就是标准的。
    

    上述就是将file文件读取,这个读取的句柄就是n.

    [root@localhost ~]# exec 3< /etc/passwd #句柄为fd3
    [root@localhost ~]# grep user <& 3
    myuser1:x:1003:1004:user one:/home/myuser1:/bin/bash
    myuser2:x:1004:1005:user two:/home/myuser2:/bin/bash
    myuser3:x:1005:1006:user two:/home/myuser3:/sbin/nologin
    pro1:x:1006:1008:projecta user:/home/pro1:/bin/bash
    pro2:x:1007:1009:projecta user:/home/pro2:/bin/bash
    pro3:x:1008:1010:projecta user:/home/pro3:/bin/bash
    

    <&:也是一种重定向操作符,用于复制输入文件描述符。

    [n] <& word 
    

    n是可选的,不写的话就是可选的。如果word是一个数字,则n表示的文件描述符作为文件描述符word的副本。

  • exec指定输出的文件描述符

    [root@localhost ~]# exec 4>log.txt
    [root@localhost ~]# date >&4 
    [root@localhost ~]# cat log.txt
    Sun Apr 17 05:32:30 EDT 2022
    [root@localhost ~]# exec 4>&-
    

二、正则表达式

正则表达式与通配符是完全不一样的东西。

正则表达式有两种类似:

  • 基本正则表达式
  • 扩展正则表达式
1. 基本正则表达式元字符
  • 元字符

    RE字符含义
    *匹配它前面的字符串和正则表达式任意次(包括0次)
    .匹配除换行符之外的任意字符一个
    ^锚头;如:“^abc”,匹配以abc开头的行
    $锚尾;比如:“abc$”,匹配以abc结尾的行
    []集合;比如:“[abc]”,匹配a或b或c
    [n1-n2]连续字符集合,下面会补充。比如:[a-z]表示,从a到z的英文字母。
    [^list]表示不匹配集合中的任意字符,比如[^abc]表示不匹配abc中任意一个
    {n,m}m要大于等于n. 匹配前一个字符n-m次。
    \转义,转义表格中特殊字符。“\$”表示$不再是锚尾的字符了。
    \<\>用于标记单词边界,比如“\<the\>”匹配单词“the”,但不匹配them等等。
    root@localhost ~]#  echo "123445" | grep "[1-3]\{2\}"
    123445 #12显示为红色。
    
2. 扩展正则表达式元字符

grep -E才支持扩展正则表达式,否则是基本正则表达式。egrep等同grep -E

RE字符含义
?匹配前面的字符,0或1次。
+匹配前面的字符至少1次。
|
()群组; 比如`egrep -n “g (la|oo)d” file.log” ;la和oo分别是一组的。
()+多个重复群组.范例:echo “AxyxyxyC”
3. POSIX字符类
特殊字符代表含义
[:alnum:]代表英文大小写字符及数字,亦即 0-9, A-Z, a-z
[:alpha:]代表任何英文大小写字符,亦即 A-Z, a-z
[:blank:]代表空白键与 [Tab] 按键两者
[:cntrl:]代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del… 等等
[:digit:]代表数字而已,亦即 0-9
[:graph:]除了空白字符 (空白键与 [Tab] 按键) 外的其他所有按键
[:lower:]代表小写字符,亦即 a-z
[:print:]代表任何可以被打印出来的字符
[:punct:]代表标点符号 (punctuation symbol),亦即:" ’ ? ! ; : # $…
[:upper:]代表大写字符,亦即 A-Z
[:space:]任何会产生空白的字符,包括空白键, [Tab], CR 等等
[:xdigit:]代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字符
4. bash正则表达式比较操作符

从bash3.0开始,bash有了内部的正则表达式比较操作符(=~)

[root@localhost ~]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

[root@localhost ~]# sh digit.sh  1234
1234 is a digit
[root@localhost ~]# sh digit.sh  1234a
Not a digit
[root@localhost ~]# sh digit.sh  a1234
Not a digit
[root@localhost ~]# cat digit.sh
#!/bin/bash
INPUT=$1
if [[ $INPUT =~ ^[0-9]+$ ]];then
        echo "$INPUT is a digit"
else
        echo "Not a digit"
fi

三 shell脚步编程

  • 运行程序的三种方式

    • 使文件有可执行的权限,可以直接运行(./执行,没x,则提示权限不够)
    • 直接调用命令解释器执行程序
    • 使用source执行文件
  • 选择解释器

    第一行中声明: #!/bin/bash 或者 #!/usr/bin/python等解释器

  • 执行原理

    一般父进程fork一个子进程,脚本在子进程中执行,不会影响父进程。 但是source (也叫点命令 .)执行的脚本,不会创建子进程,而直接在父进程中执行。 即便是在子进程中,使用export 或declare -x.也只是影响子进程中的全局变量,不会影响父进程的。

    [root@localhost ~]# cat changeName.sh
    #!/bin/bash
    echo $MYNAME
    MYNAME="blue"
    echo $MYNAME
    [root@localhost ~]# export MYNAME="joker"
    [root@localhost ~]# sh changeName.sh
    joker
    blue
    [root@localhost ~]# echo $MYNAME
    joker #可以看出 脚本并没有是外部的改变。
    
    [root@localhost ~]# source changeName.sh
    joker
    blue
    [root@localhost ~]# echo $MYNAME 
    blue #改变了
    
  • 调试脚步

    • 运行时指定-x, 搭配-v,可以输出更详细信息。

      [root@localhost ~]# sh -x digit.sh  101
      + INPUT=101
      + [[ 101 =~ ^[0-9]+$ ]]
      + echo '101 is a digit'
      101 is a digit
      + exit 0
      
    • 针对脚本内一段代码进行调试,set -x和set +x .

      [root@localhost ~]# sh run.sh
      + echo 1
      1
      + set +x
      + echo 2
      2
      + set +x
      + echo 3
      3
      + set +x
      [root@localhost ~]# cat run.sh
      #!/bin/bash
      for i in {1..3};do
         set -x
         echo ${i}
         set +x
      done
      
    • 输出更详细的调试信息:

      利用PS4变量,类似PS1.

      [root@localhost ~]# echo $PS4
      +
      

      这个+号就是调试时显示的那个+号。我们可以修改这个变量,使得提示更加详细。

      bash内置变量含义
      $LINENO表示shell脚步的当前行号
      $FUNCNAME数组变量,表示执行调用堆栈,${FUNCNAME[0]}表示函数名。

      所以可以改变成:

      export PS4='+${LINENO}:'
      
1. 变量
1. 命名规范

命名规范:以一个字母或下划线开头,后面可以跟任意长字母,数字和下划线。 如:count=0 等号两边不能有空格。 调用时:在名称前加$符, echo $count, 更规范的是:echo ${count}

2. 变量分类
  • 环境变量(全局变量):可以在创建他的的shell中以及fork出来的子进程中使用。如:PATH

    • 使用eport声明

      export  NAME="joker"
      
    • 使用declare

      declare -x Name="joker"
      
  • 局部变量:只能在父进程fork出来的进程中使用。

    NAME="joker"
    
3. 变量类型

默认情况下,在bash Shell中是不会区分变量类型的,例如:常见的变量类型为整数、字符串、小数等。默认情况下,在bash Shell中是不会区分变量类型的,例如:常见的变量类型为整数、字符串、小数等。

  • declare

    -a 声明数组变量
    -f 显示函数
    -i 声明整数型变量
    -x 设置变量为环境变量,同export命令功能相同
    -r 设置变量为只读
    
    ####声明一个整数变量
    [root@localhost ~]# declare -i myage
    [root@localhost ~]# myage="joker" 
    [root@localhost ~]# echo $myage
    0 #一定要整数才能赋值进去。
    [root@localhost ~]# myage=100
    [root@localhost ~]# echo $myage
    100
    
    ####声明一个索引数组
    [root@localhost ~]# declare -a list2='([0]="a1"  [1]="b2"  [2]=3)'
    [root@localhost ~]# echo ${list2[2]}
    3
    [root@localhost ~]# echo ${list2[1]}
    b2
    [root@localhost ~]# echo ${list2[0]}
    a1
    #或者下面两种这种才是比较常用的
    [root@localhost ~]# declare -a mylist=('joker' '2'  'man')
    [root@localhost ~]# echo ${mylist[0]}
    joker
    [root@localhost ~]# mylist=('1'  '2'  '3')
    [root@localhost ~]# echo ${mylist[2]}
    3
    [root@localhost ~]# mylist[2]=4
    [root@localhost ~]# echo ${mylist[@]}
    1 2 4
    
    ####声明一个具名数组
    [root@localhost ~]# declare -A list3='(["name"]="joker"  ["age"]=30  ["sex"]="man")'
    [root@localhost ~]# echo ${list3['name']}
    joker
    
4. 单双引号和变量
 var=1
 单引号中的变量不能被解释,'$var'就是$var字符串
 双引号: "$var"解释为1
5. 反引号解释命令
[root@localhost ~]# count=`echo "1+1" | bc`
[root@localhost ~]# echo $count
2
6. 只读变量

只读变量,使用unset是无法删除的,也无法被修改的。

  • readonly

    [root@localhost ~]# readonly mysex="man"
    -bash: unset: mysex: cannot unset: readonly variable
    
  • declare -r

    [root@localhost ~]# declare -r myclass="1301"
    [root@localhost ~]# unset myclass
    -bash: unset: myclass: cannot unset: readonly variable
    
7. 检查变量是否存在
[root@localhost ~]# echo "${varName?404}"
-bash: varName: 404
[root@localhost ~]# echo $?
1
[root@localhost ~]# varName="joker" 
[root@localhost ~]# echo "${varName?404}" 
joker #存在时,就不会报错。
8. 变量子串
表达式说明
${parameter}返回变量$parameter的内容
${#parameter}返回变量$parameter内容的长度(按字符),也使用与特殊变量
${parameter:offset}返回变量${parameter}中,从位置offset之后开始提取子串
${parameter:offset:length}从offset开始,提取长度length的子串 ${parameter#word} 从开始删除最短匹配的word子串
${parameter##word}从开头开始删除最长匹配的word子串
${parameter%word}从结尾开始删除最短匹配的word子串
${parameter%%word}从结尾开始删除最长匹配的word子串
${parameter/pattern/string}使用string代替第一个匹配的pattern
${parameter//pattern/string}使用string代替所有匹配的pattern
9. 特殊扩展变量
表达式含义
${parameter:-word}如果parameter变量值为空或未赋值,则返回word字符串作为返回值
${parameter:=word}如果parameter的变量值为空或未赋值,则设置该变量值为word,并返回该值
${parameter:?word}如果parameter的变量值为空或未赋值,那么word字符串将为作为标准错误输出,否则输出变量的值。
${parameter:+word}如果parameter的变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值。
10. 特殊位置参数变量
位置变量含义
$0获取当前执行的Shell脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径
$n获取当前执行的Shell脚本的第n个参数值,n=1…9,当n为0时表示脚本的文件名;如果n大于9,则用大括号括起来,例如${10},接的参数以空格隔开
$#获取当前执行的 Shell脚本后面接的参数的总个数
$*获取当前Shell脚本所有传参的参数,不加引号和 @ 相同 ; 如果给 ‘ @相同;如果给` @相同;如果给加上双引号,例如:"$`“,则表示将所有的参数视为单个字符串,相当于"S1 $2 $3”
$@获取当前Shell脚本所有传参的参数,不加引号和$*相同;如果给 @ 加上双弓号,例如 : " @加上双弓号,例如:" @加上双弓号,例如:"@“,则表示将所有的参数视为不同的独立字符串,相当于”$1" “$2” "“ 3 " " . . . " 。这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在会个参数里的任何空白。当 " 3" "..."。这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在会个参数里的任何空白。当" 3""..."。这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在会个参数里的任何空白。当"@”和”$*”都加双引号时,两者是有区别的;都不加双弓号时,两者无区别

"$*": 带引号的等价于: “$1$2$3…”

"$@":带引号的等价于: “$1” 、“$2”、…

11. 间接参数扩展

${!PARAMETER},被引用的不是PARAMETER自身,而是PARAMETER的值。

[root@localhost ~]# myname="joker"
[root@localhost ~]# joker="blue joker"
[root@localhost ~]# echo ${!myname}
blue joker
12. 内置变量
  • $SECONDS:脚本运行的时间

    [root@localhost ~]# vi run.sh
    [root@localhost ~]# sh run.sh
    111
    3
    [root@localhost ~]# cat run.sh
    #!/bin/bash
    echo "111"
    sleep 3
    echo ${SECONDS}
    
  • $$: 当前shell的进程id

  • $?: 上一次命令执行返回码: 0表示正常,0以外的都是错误码。

2. 算术运算符
1. 运算符
操作符(从上往下,优先级越低)用途
id++ id–变量先赋值,后加减。同js。
++id --id变量先加减,后赋值
- +单目运算符,正负号。
**幂运算
!~逻辑取反,按位取反
* / %乘 ,除,求余
+ -加,减
<< >>按位左移,按位右移
<=, >= , <,>小于等于,大于等于,小于,大于
== , !=等于,不等于
&按位与
^按位异或
|按位或
&&逻辑与
||逻辑或
expr?expr1:expr2三元运算符
=, *=,/=,%=,+=,-=,<<=, >>=,&=,^=,|=赋值操作符
expr1, expr2逗号运算符
### ++操作
[root@localhost ~]# let myid++
[root@localhost ~]# echo $myid
2
[root@localhost ~]# echo $(( 2 && 3  ))
1
[root@localhost ~]# echo $(( 2 && 0  ))
0
2. 常用进制
进制含义
let id=20默认是十进制
let id=0200开头,表示8进制,换成十进制表示16
let id=0x200x开头,表示16进制
let id=2#111#前面的表示进制,这里是2进制。所以#可以通用,也可以前面是8,16.
let id=64#cf64进制。0-9用0-9表示,10-35这个26个数字用a-z表示,36-61这个26个用A-Z表示,剩下的62和63用@和_表示。
[root@localhost ~]# let id=2#111
[root@localhost ~]# echo ${id}
7
[root@localhost ~]# echo ${id}
783
[root@localhost ~]# echo $(( 64*12 + 15 ))
783
3. 算术扩展和let
  • 算术扩展,不需要借助let直接可以进行上面的操作。格式
$(( 算术表示 ))

不支持浮点数运算。

实例:

[root@localhost ~]# id=2
[root@localhost ~]# var=$(( ++id + 10))
[root@localhost ~]# echo $var
13
#可以嵌套

[root@localhost ~]# id=100
[root@localhost ~]# var=$(( $((id%3)) + 100  ))
[root@localhost ~]# echo $var
101
  • let命令级别和算术扩展的级别相同

    但是默认在任何操作符两边不能含空格。不支持浮点数运算。

[root@localhost ~]# i=1
[root@localhost ~]# let i=i+1
[root@localhost ~]# echo $i
2

要想使用空格,必须使用双引号括起来

[root@localhost ~]# i=1
[root@localhost ~]# let "i = i+ 1"
[root@localhost ~]# echo $i
2
4. 使用expr命令

使用expr,表达式两边必须含空格。不支持浮点数运算。

这个命令还不如let和算术扩展,不推荐使用。

3. 条件语句
1. 使用test命令(即[])

test命令允许你做各种测试并每当测试成功或失败时设置它的退出状态码(0成功,1失败)

test命令可以用于:

  • 文件属性测试
  • 字符串测试
  • 算术测试

格式:

test EXPRESSION

或者

[ EXPRESSION ]

实例:

[root@localhost opt]# ls
containerd  remi  rh  vpp
[root@localhost opt]# test -d "/opt/vpp";echo $?
0
[root@localhost opt]# [ 1 == 2 ] &&  echo "equal" || echo "not equal"
not equal
[root@localhost opt]# [ 1 == 1 ] &&  echo "equal" || echo "not equal"
equal
2. 文件属性测试操作符

(test或者[]中)使用

操作符描述
-e file如果存在file则为真。exist
-f file如果file存在且是一个常规文件则为真。类型为“-”
-d file如果file存在且是目录则为真。类型为“d”,directory
-c file如果file存在且是字符设备文件则为真。类型为“c”,character
-b file同上,类型为“b”,块设备。block
-p file同上,类型为“p”,管道文件。pipe
-S file大写s ,类型为套接字,socket
-L file软链接, 类型为“l”
-h file同-L
-g file如果file设置了sgid位则为真。
-u file如果file设置了suid位则为真。
-r file如果文件存在且是可读的则为真,即权限至少4
-w file如果文件存在且是可写的则为真,即权限至少2
-x file如果文件存在且是可执行的则为真,即权限至少1
-s file如果文件存在且不为空则为真。(FILE has a size greater than zero)
-t fd如果文件描述符fd已经打开并引用了一个终端则为真。
file1 -nt file2file1比file2新则为真。mtime, nt=>newer than
file1-ot file2file1比file2旧则为真。mtime, ot=>older than
file1 -ef file2如果file1和file2的inode号相同,则为真。

实例:

#软链接
[root@localhost ~]# ls -l test.sh.ln
lrwxrwxrwx. 1 root root 7 Apr  9 12:00 test.sh.ln -> test.sh
[root@localhost ~]# [ -L test.sh.ln  ] && echo "soft link file" || echo "not soft link file"
soft link file


3. 字符串测试操作符
操作符描述
-z string如果string为空,则为真。
-n string如果string为不为空,则为真。
string1 = string2如果string1 等于string2,则为真。
string < string2如果string1的ascii码比string2小,则为真。
string > string2如果string1的ascii码比string2大,则为真。

在test和[]中 <>需要转义。

[root@localhost test]# test "abc" = "cde";echo $?
1
[root@localhost test]# test "abc" \< "def";echo $?
0
[root@localhost test]# [ "abc" \< "def" ];echo $?
0

为啥要转义呢,原因设计前,避免了和重定向符号冲突。[[ ]]这种运算符不会。

4. 算术测试操作符

针对的都是整数

操作符描述
num1 -eq num2两数相等为真。equal
num2 -ne num2不相等为真。not equal
num1 -le num2num1小于或等于num2为真。lesser or equal
num1 -ge num2num1大于或等于num2为真。greater or equal
num1 -lt num2num1小于num2为真。lesser than
num1 -gt num2num1大于num2为真。greater than
[root@localhost test]# [ 10 -eq 10 ];echo $?
0
5. if语句

格式:

if expr1;then expr2...;fi

或者

if expr1;then
	expr2
fi

或者

if expr1
then
	expr2
fi

expr1表达式的状态码为0则为真,才会往下执行。expr是命令语句,必然和你在纯命令行下执行命令是一致的。命令行下做不到的,expr1也就不正确了。

[root@localhost test]# cat run.sh
#!/bin/bash
if [ -f log.txt ];then
        echo "file exsit"
else
        echo "file not exsit"
fi
[root@localhost test]# ls
file1.txt  file2.txt  log.txt  run.sh
[root@localhost test]# sh run.sh
file exsit
6. if-else语句

同上。同样if-else可以嵌套。

if expr1;then
	expr2
else
	if expr3;then
		expr4
	else
		expr5
	fi
fi
7.elif
if expr1;then
	expr2
elif exp3;then
	expr4
else
	expr5
fi

实例如下:

[root@localhost test]# cat run.sh
#!/bin/bash
if [ -f file1.txt ];then
        exit 0
elif [ -f bak.txt ];then
        cp bak.txt file1.txt
        exit 0
else
        echo "not found file,and can not create file by bakfile"
fi
[root@localhost test]# touch bak.txt
[root@localhost test]# echo "11">bak.txt
[root@localhost test]# ls
bak.txt  run.sh
[root@localhost test]# sh run.sh
[root@localhost test]# ls
bak.txt  file1.txt  run.sh
8 逻辑与、逻辑或和逻辑非
  • &&逻辑与的几种格式

    • 命令之间

      [root@localhost test]# ls file2.txt 2>/dev/null  && echo "file exsit" || echo "file not exsit"
      file not exsit
      
    • if语句中

      [root@localhost test]# touch file2.txt
      [root@localhost test]# sh run.sh
      all right
      [root@localhost test]# cat run.sh
      #!/bin/bash
      if [ -f file1.txt ] && [ -f file2.txt ] ;then
              echo "all right"
      fi
      
    • 或者在[[ ]]

      [root@localhost test]# sh run.sh
      all right
      [root@localhost test]# cat run.sh
      #!/bin/bash
      if [[ -f file1.txt  &&  -f file2.txt ]] ;then
              echo "all right"
      fi
      

      虽然这种更强大,但不建议使用。通用性稍差。

    • test中-a选项,表示逻辑与。

      [root@localhost test]# ls
      bak.txt  file1.txt  file2.txt  run.sh
      [root@localhost test]# [ -f file1.txt -a -f file2.txt ];echo $?
      0
      [root@localhost test]# [ -f file1.txt -a -f file3.txt ];echo $?
      1
      
  • 逻辑或类似上面,使用||

    这里只讲最后一种:-o,表示other.

    [root@localhost test]# [ -f file1.txt -o -f file3.txt ];echo $?
    0
    
  • 逻辑非

    [root@localhost test]# [ ! -f file3.txt ];echo $?
    0
    
9. case语句

格式:

case EXPRESSION in
PATTERN1 )
	COMMANDS
;;
PATTERN2 )
	COMMANDS
;;
*) ;;
esac

实例:

[root@localhost test]# sh isOdd.sh 19
懒鬼
[root@localhost test]# sh isOdd.sh 100
天才
[root@localhost test]# sh isOdd.sh 10
懒鬼
[root@localhost test]# sh isOdd.sh 0
天才
[root@localhost test]# sh isOdd.sh 59
霉鬼
[root@localhost test]# sh isOdd.sh 99
普通人
[root@localhost test]# sh isOdd.sh -1
不可能为负数

#基本如下
[root@localhost test]# cat isOdd.sh
#!/bin/bash
declare -i INPUT
INPUT=$1
case "$INPUT" in
100 | 0 )
  echo "天才"
;;
[6-9][0-9])
  echo "普通人"
;;
59)
  echo "霉鬼"
;;
[1-9] | [1-5][0-9] )
  echo "懒鬼"
;;
*)
  echo "不可能为负数"
;;
esac

case语句的PATTERN是支持shell通配符和逻辑或|

4. for循环
1. for循环格式
  • 读取列表

    for var in item1 item 2 ..itemN
    do
    	cmd1
    	cmd2
    done
    

    示例:

    [root@localhost test]# sh forEp.sh
    1
    2
    3
    [root@localhost test]# cat forEp.sh
    #!/bin/bash
    for i in 1 2 3
    do
      echo $i
    done
    
    #变种1
    [root@localhost test]# sh forEp.sh
    1
    2
    3
    [root@localhost test]# cat forEp.sh
    #!/bin/bash
    for i in {1..3}
    do
      echo $i
    done
    
    #变种2
    [root@localhost test]# seq 3
    1
    2
    3
    [root@localhost test]# sh forEp.sh
    1
    2
    3
    [root@localhost test]# cat forEp.sh
    #!/bin/bash
    for i in `seq 3`
    do
      echo $i
    done
    
  • 按行读取文件

    [root@localhost test]# cat file1.txt
    1
    21
    321
    4321
    54321
    [root@localhost test]# sh forEp.sh
    line:1: 1
    line:2: 21
    line:3: 321
    line:4: 4321
    line:5: 54321
    [root@localhost test]# cat forEp.sh
    #!/bin/bash
    lineNum=1
    for i in $( < "file1.txt")
    do
      echo "line:${lineNum}:" $i
      let lineNum++
    done
    
  • c语言格式for循环

    for ((expr1;expr2;expr3))
    do
    	cmd1
    	cmd2
    done
    

    实例:

    [root@localhost test]# sh forEp.sh
    0
    1
    2
    3
    4
    [root@localhost test]# cat forEp.sh
    #!/bin/bash
    for (( i=0;i<5;i++ ))
    do
      echo $i
    done
    

    这里运用了(())和算术$(())是有区别的。

5. while循环
1. while循环格式
while  [CONDITION]
do 
 cmd1
 cmd2
done

实例

  • 读取文件
[root@localhost test]# cat file1.txt
1
21
321
4321
[root@localhost test]# sh whileEp.sh
1: 1
2: 21
3: 321
4: 4321
[root@localhost test]# cat whileEp.sh
#!/bin/bash
LINENUM=1
while IFS= read -r line
do

        echo "${LINENUM}:" $line
        let LINENUM++
done < file1.txt
  • 条件判断

    [root@localhost test]# sh whileEp2.sh
    current num: 0
    current num: 1
    current num: 2
    [root@localhost test]# cat whileEp2.sh
    #!/bin/bash
    num=0
    while [ $num -lt 3 ]
    do
       echo "current num:" $num
       let num++ #let语句中num不需要$符号。
    done
    
  • 无限循环

    借助于三个命令:true, false , :

    # 借助于true
    [root@localhost test]# sh whileEp3.sh
    we will exit with num=4
    [root@localhost test]# cat whileEp3.sh
    #!/bin/bash
    num=0
    while true
    do
      if [ $num -gt 3 ];then
         echo "we will exit with num=${num}"
         break
      else
         let num++
      fi
    
    done
    
    #借助于:
    [root@localhost test]# sh whileEp4.sh
    we will exit with num=4
    [root@localhost test]# cat whileEp4.sh
    #!/bin/bash
    num=0
    while :  #就这里变动了一下
    do
      if [ $num -gt 3 ];then
         echo "we will exit with num=${num}"
         break
      else
         let num++
      fi
    done
    
    # 使用false
    [root@localhost test]# false;echo $? #false执行后,状态码返回必定为1.所以取反则为状态码0.
    1
    [root@localhost test]# sh whileEp5.sh
    we will exit with num=4
    [root@localhost test]# cat whileEp5.sh
    #!/bin/bash
    num=0
    while ! false #只改动了这行。
    do
      if [ $num -gt 3 ];then
         echo "we will exit with num=${num}"
         break
      else
         let num++
      fi
    
    done
    
6. until循环

格式:

until [CONDITION]
do 
	cmd1
	cmd2
done

until与while对比:

  • until循环 ,如果[CONDITION]返回的状态码是0的就不执行。

    root@localhost test]# sh run.sh
    1
    2
    [root@localhost test]# cat run.sh
    #!/bin/bash
    var=1
    until [ $var -eq 3 ]
    do
      echo $var;let var++
    done
    
7. select循环

格式:

select var in List
do 
	cmd1
	cmd2
done

实例

1) date
2) w
3) hostname
4) Exit
#?
1) date
2) w
3) hostname
4) Exit
#? 1
Tue Apr 19 08:13:37 EDT 2022
#? 2
 08:13:39 up  6:56,  1 user,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/0    192.168.152.1    07:31    3.00s  0.10s  0.03s w
#? 3
localhost.localdomain
#? ^C

[root@localhost test]# cat run.sh
#!/bin/bash
select cmd in date w hostname Exit
do

select 会让用户自己选择。或者在select中写case,然程序自己自动运行。

说实话很鸡肋。

8. break和continue
  • break语句跳出循环的,for,while,until,select都可以使用break,格式如下
break [n] #n是可选的,默认跳出一层,如果嵌套多层的话,可以指定n,一次跳多层。比如 break 2表示跳出2层。
  • continue : 跳过本次循环的剩余步骤,直接进入下一次循环中。while,for,util中使用。

    continue [n];表示可以跳过几次循环。
    
9. 函数
1. 函数的格式
  • 直接使用函数名

    function_name()
    {
    	cmds
    	return n
    	#return n是可选的。如果没有return语句,这以函数最后一条命令的运行结果作为返回值。如果使用return语句,n必须在0-255之间。
    }
    
  • 使用function关键字

    #格式1
    function name {
    	cmds
    }
    #格式2
    function name() {
    	cmds
    }
    

    注意: 可以使用unset -f取消函数的定义

2. 函数的参数、调用
  • 实例:普通的调用

    [root@localhost test]# sh run.sh joker man
    name:joker--sex:man
    [root@localhost test]# cat run.sh
    #!/bin/bash
    NAME="name:$1--"
    SEX="sex:$2"
    stringJoin() {
      echo "$1$2"
    }
    
    stringJoin $NAME $SEX
    

    函数体里面使用$1,$2…来引用调用时传递给函数内部的参数。记住不要和脚本的$1,$2搞混,虽然他们形式和使用方式都一样。

  • 定义函数本地变量

    因为呀,默认情况下脚步中所有的变量都是全局的,那么函数中如果修改了某个全局变量,其他地方也会受影响。

    看个小例子:

    [root@localhost test]# sh main.sh
    women
    [root@localhost test]# cat main.sh
    #!/bin/bash
    name="man"
    changeScriptVar() {
      name="women"
    }
    changeScriptVar
    echo $name
    

​ 这时候可以使用local定义局部变量.

[root@localhost test]# sh main.sh
man #不会影响到外部的变量。
[root@localhost test]# cat main.sh
#!/bin/bash
name="man"
changeScriptVar() {
 local name="women"
}
changeScriptVar
echo $name
3. 函数的返回值和return命令

之前说了:

  • 如果没有return语句,这以函数最后一条命令的运行结果作为返回值。
  • 如果使用return语句,n必须在0-255之间。

实例一:没有使用return语句,但要报

[root@localhost test]# cat run.sh
#!/bin/bash
NAME="name:$1--"
SEX="sex:$2"
function stringJoin {
  echo "$1$2" #echo不会打印到屏幕里了,而是作为函数的结果赋值给了变量。retValue.当然如果不赋值的话,就会打印。详见实例二。
}

retValue=`stringJoin $NAME $SEX`
echo "save in retValue"
echo $retValue
[root@localhost test]# sh run.sh joker man
save in retValue
name:joker--sex:man

实例二:没有使用return语句,也不保存函数返回值。

[root@localhost test]# sh run.sh joker man
name:joker--sex:man
[root@localhost test]# cat run.sh
#!/bin/bash
NAME="name:$1--"
SEX="sex:$2"
function stringJoin {
  echo "$1$2"
}

stringJoin $NAME $SEX

实例三: 使用return语句,返回code

[root@localhost test]# sh run.sh  woman
0
[root@localhost test]# sh run.sh  man
1
[root@localhost test]# sh run.sh  renyao
2
[root@localhost test]# cat run.sh
#!/bin/bash
human=$1
function typeofHuman {
  local human=$1
  if [ $human = "woman" ];then
        return 0
  elif [ $human = 'man' ];then
       return 1
  else
       return 2
  fi
}

typeofHuman $human
echo $?
4.函数放在后台运行

在调用函数的时候,加上&,比如上面的函数

typeofHuman $human &
10. 脚步输入处理
1. 使用shopt处理大小写
shopt -s nocasematch  #开启大小写不敏感的匹配方式
shopt -u nocasematch  #关闭上面的。

这时候,我们可以在语句匹配的时候,不用担心我们的输入的大小写问题。当然也可以我们手动转换(推荐)

2. 使用shift处理命令行参数

shift命令用于将传递给脚本的多个参数向左平移。

shift [n]

n必须小于或等于KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲,即参数的数量。如果n为0,不…#,也不会操作。

比如,我们使用shift 1shift,位置参数将作如下变动:

$1 <- $2
$2 <- $3
...
$n-1 <- $N

类似js数组的的shift操作。

>>> a = [1,2,3]
>>> a.shift()
1
>>>a
[2, 3]

也就是,移出的元素,将会直接丢弃掉。

shift 5类似

$1 <- $6
$2 <- $7
...

关键一点: $#在每次操作shift后都会改变。而特殊变量$0不会参与移位操作。

3.使用for循环读取多个参数

通过特殊变量 ∗ 和 *和 @来列出传递给脚本。

[root@localhost test]# sh run.sh 1 2 3 4
LINE-1:1
LINE-1:2
LINE-1:3
LINE-1:4
[root@localhost test]# cat run.sh
#!/bin/bash
line=1
for arg in $*
do
  echo "LINE-${line}:$arg"
done
4. 选项处理getopts

使用getopts处理多命令行选项

getopts不支持长选项--

# script.sh  -x -f /etc/mybackup.conf  -r ./source.txt  ./destination.txt
  • getopts命令的基本语法:
    • OPTSTRING: 告诉getopts会有哪些选项和在哪会有参数。
    • VARNAME: 告诉getopts哪个变量用于选项报告。
    • ARGS: 告诉getopts解析这些可选的参数,而不是位置参数。
getopts OPTSTRING VARNAME [ARGS]
  • getopts会用到的三个变量:
    • OPTIND: 存放下一个要处理的参数的索引。
    • OPTARG: 这个变量被自动设置为有getopts找到的选项对应的参数
    • OPTERR: 他的值为0或1.只是bash是否应该显示由getopts产生的错误信息。默认是1,可以设置为0,就不会看到下面实例一中的错误提示信息。

例如:

$ getopts fAx VARNAME #告诉getopts查找 -f, -A和-x选项。
$ getopts fA:x VARNAME #告诉getopts,-A后面有一个参数。
  • 实例一: 传入非法的选项,会提示

    [root@localhost test]# sh run.sh -f -a -b -A -b -x
    f
    run.sh: illegal option -- a #没有-a选项,所以有报错信息。当我们不带任何选项的时候,也不会报错。
    ?
    run.sh: illegal option -- b
    ?
    A
    run.sh: illegal option -- b
    ?
    x
    [root@localhost test]# cat run.sh
    #!/bin/bash
    while getopts "fAx" opt
    do
       echo $opt
    done
    
  • 实例二:不带参数的选项给他传参。

    [root@localhost test]# sh run.sh -f -x
    f
    x
    [root@localhost test]# cat run.sh
    #!/bin/bash
    while getopts "fA:x" opt
    do
       echo $opt
    done
    [root@localhost test]# sh run.sh -f -x
    f
    x
    [root@localhost test]# sh run.sh -f -x joker
    f
    x
    [root@localhost test]# sh run.sh -f joker  -x joker
    f  #只打印了f,后面的被忽略了。
    

完整案例:填写隔热用户信息的脚步

[root@localhost test]# sh run.sh -t
Invalid option: -t
USAGE:
      run.sh  [-n <name>] [-a <age>] [-s <sex>]
          -n input a string to set your name
          -a input a number to set your age
          -s input 'man' or 'woman' to set your sex
[root@localhost test]# vi run.sh
[root@localhost test]# sh run.sh -n
The option -n require an argument
[root@localhost test]# sh run.sh -n joker
name:joker
age:
sex:
[root@localhost test]# sh run.sh -n joker -a 30 -s man
name:joker
age:30
sex:man
[root@localhost test]# cat run.sh
#!/bin/bash
name=
age=
sex=

function usage() {
  echo "USAGE:"
  echo "      $0  [-n <name>] [-a <age>] [-s <sex>]"
  echo "          -n input a string to set your name"
  echo "          -a input a number to set your age"
  echo "          -s input 'man' or 'woman' to set your sex"
  exit 1
}

while getopts  :n:a:s: opt #最后一个冒号要注意了。
do
   case $opt in
     n)
        name=$OPTARG
     ;;
     a)
        age=$OPTARG
     ;;
     s)
        sex=$OPTARG
     ;;
     :) #重点1
        echo "The option -$OPTARG require an argument"
        exit 1
     ;;
     ?) #重点2
        echo "Invalid option: -$OPTARG" #重点3
        usage
        exit 2
     ;;
   esac
done

echo "name:$name"
echo "age:$age"
echo "sex:$sex"

注意以上脚本除了之前所说的,还有三个注意点:

  • 重点1: 冒号表示,如果没有为需要参数的选项指定参数,则显示提示信息,并退出脚本的允许。
  • 重点2: 问号表示,若指定的选项为无效选项(没有再getopts中指定的),则可以通过问号匹配,来自定义错误信息。
  • 重点3: 如果匹配到了选项和参数,$OPTARG则是参数;如果么有参数,$OPTARG表示选项。
5. 选项处理getopt

getopt命令与getopts相似。

  • getopt支持长选项(–some-option)
  • getopt调用的方式不同

格式:

getopt [options]  [--] optstring parameters
getopt [options] -o|--options optstring  [options] [--] parameters

案例1:

$ getopt f:mt  -mt -f /tmp/testDir  200
 -m -t -f /tmp/testDir -- 200
  • f:mt对应getopt中的optstring(选项字符串)
  • -mt-f /tmp/testDir 200对应getopt命令语法中的parameters(getopt命令的参数)

getopt会按照optstring的设置,将parameters解析为相应的选项和参数。

  • f:, f后面有个冒号,所有-f /tmp/testDir 被解析成-f /tmp/testDir ,然后解析后的命令行选项和参数之间使用双连字符(–)分隔。

案例2: 使用shift和$@获取参数。

[root@localhost test]# sh main.sh  -mt -f /tmp/testDir/file1 200  -d /tmp/testDir 100
-m
-t
-f
/tmp/testDir/file1
-d
/tmp/testDir
--
200
100
[root@localhost test]# cat main.sh
#!/bin/bash
set -- `getopt d:f:mt "$@"`

while [ $# -gt 0 ]
do
  echo $1
  shift
done
  • 长字符

    #!/bin/bash
    ARG_B=0
    eval set -- `getopt -o  a::bc: --long arga::,argb,argc: -n 'error.sh' -- "$@"` 
    
    while true
    do 
      case "$1" in 
        -a|--arga)
          case "$2" in
            "")
                ARG_A='default value'
                shift
                ;;
            *)
              ARG_A=$2
              shift
              ;;
          esac
          ;;
        -b|--argb)
          ARG_B=1
        ;;
        -c|--argc)
          case "$2" in 
            "")
              shift
              ;;
            *)
              ARG_C=$2
              shift
              ;;
          esac
          ;;
        --)
          shift
          break
          ;;
        *)
          echo "error options"
          exit 1
      esac
      shift
    done
    
    echo "ARG_A = $ARG_A"
    echo "ARG_B = $ARG_B"
    echo "ARG_C = $ARG_C"
    

getopt命令的-o选项,用来指定短选项(一个字符的)

  • 每一个字符表示一个选项
  • 字符后跟一个冒号(:)表示选项需要一个参数,此时选项和参数之间可以有空格。
  • 字符后跟两个冒号(::)表示选项有个可选参数,此时选项和参数之间不能有空格。
  • 字符后面没有冒号表示不需要参数。
[root@localhost test]# sh   main.sh -a -b -c 456
ARG_A =
ARG_B = 0
ARG_C =
[root@localhost test]# sh   main.sh -a -b -c 456
ARG_A = default value
ARG_B = 1
ARG_C = 456
[root@localhost test]# sh   main.sh -a123 -b -c 456 
ARG_A = 123
ARG_B = 1
ARG_C = 456
[root@localhost test]# sh   main.sh -c 456
ARG_A =
ARG_B = 0
ARG_C = 456
[root@localhost test]# sh   main.sh -a 123 -b -c 456 #必须-a和123无空格,否则无法识别。
ARG_A = default value
ARG_B = 1
ARG_C = 456

getopt命令的--long选项所指定的选项字符串遵循如下规则:

  • 每个选项之间由逗号(,)分隔。
  • 字符串后跟一个冒号(:)表示选项需要一个参数(–argc='parma1’或 --argc param1)。
  • 字符串后跟两个冒号(::)表示选项有个可选参数(–arga=‘parma1’), 等号是必须的。
[root@localhost test]# sh main.sh --argc=345
ARG_A =
ARG_B = 0
ARG_C = 345
[root@localhost test]# sh main.sh --argc 345
ARG_A =
ARG_B = 0
ARG_C = 345
[root@localhost test]# sh main.sh --argc 345 --arga=32
ARG_A = 32
ARG_B = 0
ARG_C = 345

四、 shell一些特殊符号和语法

1. 大括号
  • 生成多个字符串

    [root@localhost ~]# echo user{one,two,three}
    userone usertwo userthree
    [root@localhost ~]# echo 2021-{01..12}
    2021-01 2021-02 2021-03 2021-04 2021-05 2021-06 2021-07 2021-08 2021-09 2021-10 2021-11 2021-12
    
  • 创建多个文件

    [root@localhost test]# touch user-{1..5}.log
    [root@localhost test]# ls
    user-1.log  user-2.log  user-3.log  user-4.log  user-5.log
    
  • 上述语法,在bash 4.0之后,还可以指定步长。

    [root@localhost test]# echo {1..10..2}
    1 3 5 7 9
    
2. IFS等等

五、学习中写的一些脚本

1. 判断是否是一个数字
[root@localhost ~]# sh digit.sh  1010
1010 is a digit
[root@localhost ~]# sh digit.sh  1012x
Not a digit
[root@localhost ~]# cat digit.sh
#!/bin/bash
INPUT=$1
if [[ $INPUT =~ ^[0-9]+$ ]];then
        echo "$INPUT is a digit"
        exit 0
else
        echo "Not a digit"
        exit 1
fi
2. case语句判断学生成绩分类
[root@localhost test]# cat isOdd.sh
#!/bin/bash
declare -i INPUT
INPUT=$1
case "$INPUT" in
100 | 0 )
  echo "天才"
;;
[6-9][0-9])
  echo "普通人"
;;
59)
  echo "霉鬼"
;;
[1-9] | [1-5][0-9] )
  echo "懒鬼"
;;
*)
  echo "不可能为负数"
;;
esac
3. for循环和while按行读取文件
  • for循环
[root@localhost test]# cat forEp.sh
#!/bin/bash
lineNum=1
for i in $( < "file1.txt")
do
  echo "line:${lineNum}:" $i
  let lineNum++
done
  • while

    [root@localhost test]# cat whileEp.sh
    #!/bin/bash
    LINENUM=1
    while IFS= read -r line
    do
    
            echo "${LINENUM}:" $line
            let LINENUM++
    done < file1.txt
    

后记

1. md5sum
2. env
3. export
4. set
5. declare
[dmtsai@study ~]$ declare [-aixr] variable
选项与参数:
-a :将后面名为 variable 的变量定义成为阵列 (array) 类型
-i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型
-x :用法与 export 一样,就是将后面的 variable 变成环境变量;
-r :将变量设置成为 readonly 类型,该变量不可被更改内容,也不能 unset
范例一:让变量 sum 进行 100+300+50 的加总结果
[dmtsai@study ~]$ sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
100+300+50 <==咦!怎么没有帮我计算加总?因为这是文字体态的变量属性啊!
[dmtsai@study ~]$ declare -i sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
450 
6. locale
7. read
[dmtsai@study ~]$ read [-pt] variable
选项与参数:
-p :后面可以接提示字符!
-t :后面可以接等待的“秒数!”这个比较有趣~不会一直等待使用者啦!
范例一:让使用者由键盘输入一内容,将该内容变成名为 atest 的变量
[dmtsai@study ~]$ read atest
This is a test #;==此时光标会等待你输入!请输入左侧文字看看
8. typeset
9. 数组
范例:设置上面提到的 var[1] ~ var[3] 的变量。
[dmtsai@study ~]$ var[1]="small min"
[dmtsai@study ~]$ var[2]="big min"
[dmtsai@study ~]$ var[3]="nice min"
[dmtsai@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min
10. ulimit
[dmtsai@study ~]$ ulimit [-SHacdfltu] [配额]
选项与参数:
-H :hard limit ,严格的设置,必定不能超过这个设置的数值;
-S :soft limit ,警告的设置,可以超过这个设置值,但是若超过则有警告讯息。
在设置上,通常 soft 会比 hard 小,举例来说,soft 可设置为 80 而 hard
设置为 100,那么你可以使用到 90 (因为没有超过 100),但介于 80~100 之间时,
系统会有警告讯息通知你!
-a :后面不接任何选项与参数,可列出所有的限制额度;
-c :当某些程序发生错误时,系统可能会将该程序在内存中的信息写成文件(除错用),
这种文件就被称为核心文件(core file)。此为限制每个核心文件的最大容量。
-f :此 shell 可以创建的最大文件大小(一般可能设置为 2GB)单位为 KBytes
-d :程序可使用的最大断裂内存(segment)容量;
-l :可用于锁定 (lock) 的内存量
-t :可使用的最大 CPU 时间 (单位为秒)
-u :单一使用者可以使用的最大程序(process)数量。
范例一:列出你目前身份(假设为一般帐号)的所有限制数据数值
[dmtsai@study ~]$ ulimit -a
core file size (blocks, -c) 0 #==只要是 0 就代表没限制
data seg size (kBytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited #==可创建的单一文件的大小
pending signals (-i) 4903
max locked memory (kBytes, -l) 64
max memory size (kBytes, -m) unlimited
open files (-n) 1024 #==同时可打开的文件数量
pipe size (512 Bytes, -p) 8
POSIX message queues (Bytes, -q) 819200
real-time priority (-r) 0
stack size (kBytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kBytes, -v) unlimited
file locks (-x) unlimited
范例二:限制使用者仅能创建 10MBytes 以下的容量的文件
[dmtsai@study ~]$ ulimit -f 10240
[dmtsai@study ~]$ ulimit -a | grep 'file size'
core file size (blocks, -c) 0
file size (blocks, -f) 10240 #==最大量为10240Kbyes,相当10MBytes
[dmtsai@study ~]$ dd if=/dev/zero of=123 bs=1M count=20
File size limit exceeded (core dumped) #==尝试创建 20MB 的文件,结果失败了
11. unalias
12. history
[dmtsai@study ~]$ history [n]
[dmtsai@study ~]$ history [-c]
[dmtsai@study ~]$ history [-raw] histfiles
选项与参数:
n :数字,意思是“要列出最近的 n 笔命令列表”的意思!
-c :将目前的 shell 中的所有 history 内容全部消除
-a :将目前新增的 history 指令新增入 histfiles 中,若没有加 histfiles ,
则默认写入 ~/.bash_history
-r :将 histfiles 的内容读到目前这个 shell 的 history 记忆中;
-w :将目前的 history 记忆内容写入 histfiles 中!
范例一:列出目前内存内的所有 history 记忆
[dmtsai@study ~]$ history
# 前面省略
1017 man bash
1018 ll
1019 history
1020 history
# 列出的信息当中,共分两栏,第一栏为该指令在这个 shell 当中的代码,
# 另一个则是指令本身的内容喔!至于会秀出几笔指令记录,则与 HISTSIZE 有关!
范例二:列出目前最近的 3 笔数据
[dmtsai@study ~]$ history 3
1019 history
1020 history
1021 history 3
范例三:立刻将目前的数据写入 histfile 当中
[dmtsai@study ~]$ history -w
# 在默认的情况下,会将历史纪录写入 ~/.bash_history 当中!
[dmtsai@study ~]$ echo ${HISTSIZE}
1000

和history配置到的操作,很重要。

[dmtsai@study ~]$ !number
[dmtsai@study ~]$ !command
[dmtsai@study ~]$ !!
选项与参数:
number :执行第几笔指令的意思;
command :由最近的指令向前搜寻“指令串开头为 command”的那个指令,并执行;
!! :就是执行上一个指令(相当于按↑按键后,按 Enter)

我们就可以执行之前的命令了,不用复制粘贴(在某些环境下,没办法赋值粘贴)

[root@localhost ~]# history
    1  history
    2  ls
    3  history
[root@localhost ~]# !2
ls
anaconda-ks.cfg  ls.txt      nginx.conf.copy  raysweb  test.txt
dockerfile       nginx.conf  nginx.conf.link  test
13. /etc/issue
14. /etc/motd
15. source或点

​ 利用 source 或小数点 (.) 都可以将配置文件的内容读进来目前的 shell 环境中

16. /etc/man_db.conf
17. ~/.bash_logout
18. /dev/null
19. sync

Linux sync命令用于数据同步,sync命令是在关闭Linux系统时使用的。

[root@localhost ~]# sync;sync

关机前执行两次sync。

20. tr
21. col
22. join
23. paste
24.extend
25. wc
26. uniq
27 sort
28. split
29. xargs
30. diff
[dmtsai@study ~]$ diff [-bBi] from-file to-file
选项与参数:
from-file :一个文件名,作为原始比对文件的文件名;
to-file :一个文件名,作为目的比对文件的文件名;
注意,from-file 或 to-file 可以 - 取代,那个 - 代表“Standard input”之意。
-b :忽略一行当中,仅有多个空白的差异(例如 "about me""about me" 视为相同
-B :忽略空白行的差异。
-i :忽略大小写的不同。
范例一:比对 passwd.old 与 passwd.new 的差异:
[dmtsai@study testpw]$ diff passwd.old passwd.new
31. cmp
[dmtsai@study ~]$ cmp [-l] file1 file2
选项与参数:
-l :将所有的不同点的字节处都列出来。因为 cmp 默认仅会输出第一个发现的不同点。
范例一:用 cmp 比较一下 passwd.old 及 passwd.new
[dmtsai@study testpw]$ cmp passwd.old passwd.new
passwd.old passwd.new differ: char 106, line 4
32. :命令
[root@localhost test]# :
[root@localhost test]# echo $?
0 #执行成功。
[root@localhost test]# gaga
-bash: gaga: command not found
[root@localhost test]# echo $?
127

:命令执行后,不做任何事情。是bash的内置命令。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当学习Shell命令时,以下是一些重要的注意事项和学习建议: 1. 基本语法:Shell命令通常由命令名称和参数组成。命令名称是要执行的操作,而参数则提供了命令所需的信息。了解Shell命令的基本语法是学习的第一步。 2. 命令帮助:大多数Shell都提供了内置的命令帮助功能。通过使用命令后跟`--help`或`-h`选项,或者使用`man`命令(例如`man ls`)来获取有关特定命令的详细信息。 3. 常用命令:有一些常见的Shell命令对于日常使用非常有用。这些包括`cd`(更改目录)、`ls`(列出目录内容)、`mkdir`(创建目录)、`rm`(删除文件或目录)等。逐步学习和掌握这些命令是很重要的。 4. 管道和重定向:Shell命令非常强大,可以通过管道和重定向符号将多个命令组合在一起。了解如何使用管道(`|`)将一个命令的输出作为另一个命令的输入,并使用重定向符号(`>`、`>>`、`<`)来控制输入和输出。 5. 脚本编写:Shell脚本是一种将多个命令组合在一起以自动化任务的方法。学习如何编写简单的Shell脚本可以提高工作效率。 6. 实践和练习:最重要的是进行实践和练习。尝试使用不同的Shell命令来完成各种任务,并在实际场景中应用所学知识。 记住,Shell命令的学习是一个逐步的过程。开始时可能会有些困惑,但随着实践和经验的积累,你会变得越来越熟练。希望这些提示对你有帮助!如有任何具体问题,随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值