美文网首页
shell 随笔

shell 随笔

作者: 欧阳_z | 来源:发表于2020-09-22 08:42 被阅读0次

    1、man
    manual 是类 UNIX 系统的标准手册工具,共 9 卷,用 man man可以看到 9 卷分别是什么内容:

       1   Executable programs or shell commands 可执行程序或shell命令
       2   System calls (functions provided by the kernel)  系统调用
       3   Library calls (functions within program libraries) 库调用
       4   Special files (usually found in /dev) 特殊文件
       5   File formats and conventions eg /etc/passwd 文件格式和约定
       6   Games 游戏
       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) 杂项(包括宏和惯例)
       8   System administration commands (usually only for root) 系统管理命令(通常仅适用于root用户)
       9   Kernel routines [Non standard] 内核例程(非标准)
    

    如果只有一个含义,直接查询即可,比如man ls,但有很多是一词多义的:chmod, chown, chmod, stat, exit, info, socket, flock, open, write, printf, passwd 等等。
    man 1 printf 表示 shell 命令,
    man 3 printf 表示 C 语言标准库函数,
    可以用 -a 选项显示全部,比如 man -a printf ,按 q 退出之后,可以按回车键表示继续看下一卷,或按 Ctrl-C 直接退出。

    2、#!/bin/bash
    #!/bin/bash放在 shell 脚本开头,是指此脚本使用/bin/bash来解释执行。

    #!/usr/bin/python 也是类似的, 表示用/usr/bin/python来解释执行 python 脚本,加了这行就可以用./xx.py的方式来运行了。

    #!/usr/bin/env python 会去环境变量路径中寻找 python 目录。

    3、shell 运行方式

    (1) bash 1.sh
    创建子进程运行 1.sh,需要有 r 权限。
    (2) ./1.sh
    创建子进程运行 1.sh,需要有 rx 权限。
    (3) source 1.sh
    当前进程运行 1.sh,需要有 r 权限,
    (4) . 1.sh
    点命令是 source 命令的另一种写法。

    如何证明是在当前进程运行,还是创建子进程运行呢,下面用 2 种简单的方法:
    (1) 创建一个子目录,然后用 cd 命令

    ~/test$ mkdir child
    ~/test$ echo 'cd child;pwd' > 1.sh
    

    使用 bash 执行结束后,可以看左边的提示符,当前的工作目录没有改变,cd 命令只对子进程产生影响:

    ~/test$ bash 1.sh
    /home/oo/test/child
    ~/test$ 
    

    使用 source执行结束后,当前目录变成了 child 目录,因为 cd 命令对当前进程产生了影响:

    ~/test$ source 1.sh
    /home/oo/test/child
    ~/test/child$ 
    

    (2) 变量赋值
    bash是创建子进程来执行脚本,所以子进程的变量,在父进不能访问;
    source在当前进程执行,所以脚本中对变量的赋值,当前进程能访问:

    $ echo "a=99" > 1.sh
    $ bash 1.sh 
    $ echo $a
    
    $ source 1.sh
    $ echo $a
    99
    

    4、内部命令,外部命令
    通过 type 命令可以查询某个命令是内部命令、外部命令,还是别名(alias):

    $ type cd
    cd is a shell builtin
    $ type find
    find is /usr/bin/find
    $ type ls
    ls is aliased to `ls --color=auto'
    

    如果用 bash shell,可以通过help命令列出所有内部命令列表。

    区别:
    (1)内部命令不创建子进程,对当前进程有影响,执行速度快;
    外部命令不在shell中,保存在一个独立的文件中,会创建新的进程来执行。
    (2)内部命令查询帮助信息用:help 内部命令,比如 help cd
    外部命令查询帮助信息用:外部命令 --help,比如 ls --help

    当然除了 manhelp 之外,info 也可以用于查询帮助信息。

    5、重定向

    (1) 输入重定向 <

    $ wc -l < /etc/passwd
    32
    
    $ echo 123 > 1.txt
    $ read var < 1.txt 
    $ echo $var
    123
    

    (2) 输出重定向
    ① > 先清空该文件,再追加内容。
    ② >> 文件的内容不变,在文件末尾追加内容。
    ③ 2> 错误重定向

    $ nothing
    nothing: command not found
    
    $ nothing 2> 1.txt
    $ cat 1.txt 
    nothing: command not found
    

    ④ &> 表示无论是正确还是错误,都输出重定向。

    (3) 输入、输出重定向的组合使用

    将开始标记 和结束标记之间的内容作为 cat的输入,cat 的输出重定向到文件 1.txt:

    $ cat > 1.txt << EOF
    > 123
    > 456
    > EOF
    $ cat 1.txt 
    123
    456
    

    这里的 EOF 只是开始和结尾的 tag,也可以换成其他的,比如ABCD:

    $ cat > 1.txt << ABCD
    > 1234
    > 5678
    > ABCD
    $ cat 1.txt 
    1234
    5678
    

    6、变量
    (1) 赋值, = 的两边不能有空格,否则被解释为命令:

    $ a=123
    $ echo $a
    123
    $ a =123
    a: command not found
    $ a= 123
    123: command not found
    

    (2) 算数运算用 let ,否则默认是字符串

    $ a=1+2+3
    $ echo $a
    1+2+3
    
    $ let a=1+2+3
    $ echo $a
    6
    

    (3) 把命令赋值给变量,意义不大:

    $ a=ls
    $ $a -l
    

    (4) 把命令的结果赋值给变量:

    $ a=$(find . -name *.sh)
    $ echo $a
    

    (5) 作用范围
    变量只在当前 shell 生效,子shell、父shell 都不能共用。
    使用 export 命令之后,子shell 可以获取 父shell 的变量。其他终端不可以。

    (6)变量不用了之后,可以删除: unset 变量名。

    7、环境变量

    (1)env 命令可以列出所有环境变量,一般是大写。

    PATH 指定命令搜索路径,
    PS1 可以使当前终端显示更友好,
    UID 用户ID,
    USER 用户名。
    PWD 当前路径,
    LD_LIBRARY_PATHLD_PRELOAD 程序运行时,搜索动态库路径。

    (2) set 命令可以列出系统中已经存在的 shell 变量,比 env 命令列出的变量更多。

    $ abcde=1234567
    $ set | grep abcde
    abcde=1234567
    

    (3) 预定义变量
    $$ :当前进程的PID号
    $0 :当前程序或脚本的名称
    $? :用于返回上一条命令是否成功执行。如果成功执行,将返回数字0,否则返回非零数字(通常情况下都返回数字1)。
    $*$@ :保存传递给脚本或进程的所有参数
    $# :脚本参数的个数,(类似C语言的 argc)
    $1$2${10} 等等 :位置变量,从命令行给脚本传参(类似C语言的argv[]),还有函数传参时会使用。

    (4) 环境变量的配置文件
    /etc/profile 是所有用户共用的。
    ~/.bashrc 是某一个用户特有的。
    如果存在重复定义的变量,/etc/profile 先执行,会被后执行的 ~/.bashrc 覆盖。万一有变量被重复定义了,执行顺序很重要,可以在这 2 个文件中添加echo 输出重定向到文件,来判断谁先被执行。

    另外:
    su root 是 non-login shell,不会加载所有的配置文件,不建议使用;
    su - root 是 login shell,会加载所有配置文件,推荐使用这种方式。

    8、数组
    定义数组用小括号,例如: a=(1 2)
    访问所有元素 ${a[@]}
    数组元素的个数 ${#a[@]}
    访问单个元素 ${a[0]},负号表示倒数第几个 ${a[-1]}

    $ a=( 11 22 33 )
    $ echo ${a[@]}
    11 22 33
    $ echo ${a[*]}
    11 22 33
    $ echo ${#a[@]}
    3
    $ echo ${a[0]}
    11
    $ echo ${a[1]}
    22
    $ echo ${a[-1]}
    33
    $ echo ${a[-3]}
    11
    

    9、特殊字符
    # 注释
    ; 分号用来分隔两条命令。
    ` 反引号 与 $() 都是用来执行命令的。
    " 双引号(不完全引用)会对变量解释,
    ' 单引号(完全引用)不解释变量。

    $ a=hello
    $ echo "$a"
    hello
    $ echo '$a'
    $a
    

    10、整数运算
    默认会解释为字符串,所以需要用 expr,注意空格:

    $ expr 1 + 2 + 3
    6
    $ expr 1+ 2 + 3
    expr: syntax error
    

    除了 + 还有 - * / % > < 等等,注意转义字符:

    $ expr 1 - 2
    -1
    $ expr 6 \* 6
    36
    $ expr 30 / 6
    5
    $ expr 32 % 3
    2
    $ expr 3 \> 4
    0
    $ expr 3 \< 4
    1
    $ expr 3 \<= 3
    1
    $ expr 3 \!= 3
    0
    $ expr 3 \!= 4
    1
    

    不支持小数运算:

    $ expr 5 + 0.5
    expr: non-integer argument
    

    另外 length 可以计算字符串长度:

    $ expr length "hello"
    5
    

    **表示乘方,expr没有乘方功能:

    $ let a=2**3
    $ echo $a
    8
    

    let 简写为双圆括号:

    $ ((a=5+6))
    $ echo $a
    11
    

    11、园括号()里面的赋值,不会影响外面。

    $ a=10;echo $a
    10
    $ (a=20;echo $a)
    20
    $ echo $a
    10
    

    是圆括号()创建了一个子进程吗,还是其他原因呢?
    可以做一个小实验,先打开 2 个终端:
    先查看有几个 bash 进程,可以看到是 5 个:

    $ ps aux |grep bash |wc -l
    5
    $ (a=20;echo $a;sleep 9)
    

    把圆括号睡眠一下,趁这个时间再次查看 bash 数量,比刚才多了 1 个,等 sleep 结束之后再看 bash 数量又恢复 5个,说明圆括号()确实创建了子进程:

    $ ps aux |grep bash |wc -l
    6
    

    另外也可以再通过 cd 命令来看,圆括号中改变了工作目录,并不影响父进程:

    $ pwd
    /home/oo/test
    oo@uu:~/test$ (a=20; cd ..; pwd)
    /home/oo
    oo@uu:~/test$ pwd
    /home/oo/test
    

    12、test 命令
    test 命令是通过设置 $? 的值, 来让我们可以判断是否正常退出,或者条件是否为真,0 为 真,非 0 为假。

    test 通常有三类测试:字符串、数字、文件,可以通过 man 查询具体用法:
    man test|grep INTEGER
    man test|grep STRING
    man test|grep FILE

    通过 man test 可以看到 test EXPRESSION[ EXPRESSION ] 两种写法,
    有时为了程序美观,可以把 test 换成方括号[ ] 的写法。

    (1) 字符串
    字符串是否相等,会区分大小写:

    $ [ "abc" = "abc" ]
    $ echo $?
    0
    $ [ "abc" = "a" ]
    $ echo $?
    1
    

    (2) 整数
    [] 扩展为 [[]] ,可以支持更多运算符。

    $ test 3 -gt 4
    $ echo $?
    1
    $ [ 3 -gt 4 ]
    $ echo $?
    1
    $ [[ 3 > 4 ]]
    $ echo $?
    1
    

    (3) 文件
    文件是否存在:

    $ test -f /etc/passwd
    $ echo $?
    0
    $ test -f /etc/passwd.1234
    $ echo $?
    1
    

    目录存在:

    $ test -d /etc
    $ echo $?
    0
    

    文件或目录存在:

    $ test -e /etc
    $ echo $?
    0
    $ test -e /etc/passwd
    $ echo $?
    0
    

    13、大括号
    (1) 表示某个范围内的值,主要是给 for 循环使用的

    $ echo {0..3}
    0 1 2 3
    

    (2) 减少重复书写
    cp -v /etc/passwd{,.bak} 相当于 cp -v /etc/passwd /etc/passwd.bak

    14、if
    (1)if-then-fi
    (2)if-then-else-fi
    (3)if-then-elif-then-else-fi

    let a=1-6
    if [ $a -eq 0 ];
    then
        echo "a=0"
    elif [ $a -lt 0 ];
    then
        echo "a<0"
    else
        echo "a>0"
    fi
    

    15、case-esac

    a=3
    case "$a" in
    1)
    echo "case 1"
    ;;
    2|3)
    echo "case 2 or 3"
    ;;
    *)
    echo "case *"
    ;;
    esac
    

    case 语句的例子在很多文件都有,比如 grep -n case ~/.bashrc

    16、for

    for i in {1..3}
    do
        echo $i
    done
    
    for filename in /etc/profile.d/*.sh
    do
        if [ -x $filename ]; then
            source $filename;
            echo "source $filename;";
        fi
    done
    

    下面是 C 语言风格的写法:

    for ((i=1;i<=3;i++))
    do
        echo $i
    done
    

    17、while

    a=1
    while [ $a -le 3 ];
    do
        echo $a;
        ((a++));
    done
    

    用冒号表示条件为真,写一个死循环:

    while :
    do
        echo "always"
    done
    

    18、until
    until 循环与 while 语法一致,只是条件为假才执行循环:

    a=8
    until [ $a -lt 5 ];
    do
        echo $a;
        ((a--));
    done
    

    19、break 与 continue

    for n in {1..5}
    do
        if [ $n -eq 3 ];then
            break;
        fi
        echo $n
    done
    
    for n in {1..5}
    do
        if [ $n -eq 3 ];then
            continue;
        fi
        echo $n
    done
    

    20、字符串截取命令
    basename 命令,可以用来删掉后缀名:

    $ basename hello.c.diff .diff
    hello.c
    $ basename hello.c.diff .c.diff
    hello
    

    dirname用于获取目录

    $ dirname /etc/hosts
    /etc
    

    其实它们不在乎这个目录是否真实存在,只是简单的处理一下字符串而已:

    $ dirname /1/2/3
    /1/2
    

    21、函数
    函数定义的格式:
    函数名(){ }

    $ testfun(){ echo hello;};testfun;
    hello
    

    避免函数内部的变量影响到外面,使用 local 关键字限定作用域为该函数。

    相关文章

      网友评论

          本文标题:shell 随笔

          本文链接:https://www.haomeiwen.com/subject/heliyktx.html