1. shell学习目标

  • 方便看懂运维人员编写的Shell程序。
  • 用于编写一些简单Shell程序来管理集群、提高开发效率。

    2. shell解析器

  1. 查看Linux提供的Shell解析器。 ```bash cat /etc/shells

结果如下:

/bin/sh

/bin/bash

/sbin/nologin

/bin/dash

/bin/tcsh

/bin/csh

  1. 2. bashsh的关系。
  2. ```bash
  3. cd /bin
  4. ll | grep bash
  5. # 结果如下:
  6. # bash
  7. # sh -> bash
  1. Linux系统默认的解析器是bash,Unix默认解释器是csh。 ```bash echo $SHELL

结果如下:

/bin/bash # linux

/bin/csh # unix

  1. <a name="P8e7t"></a>
  2. # 3. shell脚本入门
  3. 1. 脚本格式。
  4. 脚本以“#!/bin/bash”开头(指定Shell脚本解释器的路径)。**如果一个script只是一些普通linux指令的堆砌。那么#!可以略去不写。但通常我们遇到的都不是这种情况。如果这个script中包含一些自定义的程序组件,比如说函数,变量等,#!便需要标注。**
  5. 2. 第1个Shell脚本:hello world。
  6. 需求:创建一个Shell脚本,输出hello world。
  7. ```bash
  8. touch helloworld.sh
  9. vi helloworld.sh

在helloworld.sh中输入如下内容:

  1. #!/bin/bash
  2. echo "helloworld"
  1. 脚本的常用执行方式。
  • 方式1:采用bash或sh+脚本的相对路径或绝对路径(不用赋予脚本+x权限)

    1. # sh+脚本的相对路径
    2. sh helloworld.sh
    3. # sh+脚本的绝对路径
    4. sh /home/polaris/datas/helloworld.sh
    5. # bash+脚本的相对路径
    6. bash helloworld.sh
    7. # bash+脚本的绝对路径
    8. bash /home/polaris/datas/helloworld.sh
  • 方式2:采用输入脚本的绝对路径或相对路径执行脚本(必须具有可执行权限+x)

    1. # 首先要赋予helloworld.sh 脚本的+x权限
    2. chmod 755 helloworld.sh
    3. # 执行脚本
    4. ## 相对路径
    5. ./helloworld.sh
    6. ## 绝对路径
    7. /home/polaris/datas/helloworld.sh

    注意:第一种执行方法,本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限。第二种执行方法,本质是脚本需要自己执行,所以需要执行权限。

  1. 示例:多命令处理

需求: 在家目录下创建一个banzhang.txt,在banzhang.txt文件中增加“I am polaris!”。

  1. touch batch.sh
  2. vi batch.sh

在batch.sh中输入如下内容:

  1. #!/bin/bash
  2. cd ~
  3. touch polaris.txt
  4. echo "I am polaris!" >> polaris.txt

4. shell中的变量

4.1. 系统变量

常用系统变量,包括$HOME、$PWD、$SHELL、$USER等。

  1. # 查看系统变量的值
  2. echo $HOME
  3. # 显示当前Shell中所有变量
  4. set
  5. # 结果如下:
  6. # BASH=/bin/bash
  7. # BASH_ALIASES=()
  8. # BASH_ARGC=()
  9. # BASH_ARGV=()

4.2. 自定义变量

  1. 语法。
  • 定义变量:变量=值
  • 撤销变量:unset 变量
  • 声明静态变量:readonly变量,注意:静态变量不能unset。
  1. 变量定义规则。
  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。
  • 等号两侧不能有空格。
  • 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算。
  • 变量的值如果有空格,需要使用双引号或单引号括起来。
  1. 案例实操。 ```bash

    定义变量A

    A=5 echo $A

给变量A重新赋值

A=8 echo $A

撤销变量A

unset A echo $A

声明静态的变量B=2,不能重新赋值和unset

readonly B=2 echo $B B=9

结果如下(异常):

-bash: B: readonly variable

在bash中,变量默认类型都是字符串类型,无法直接进行数值运算

C=1+2 echo $C

变量的值如果有空格,需要使用双引号或单引号括起来

D=I love polaris

结果如下(异常):

-bash: world: command not found

D=”I love polaris” echo $D

可把变量提升为全局环境变量,可供其他Shell程序使用

export 变量名

  1. <a name="WW3iO"></a>
  2. ## 4.3. 特殊变量
  3. <a name="EbrWS"></a>
  4. ### 1. $n
  5. 功能描述:n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10}。
  6. ```bash
  7. # 输出该脚本文件名称、输入参数1和输入参数2的值
  8. touch parameter.sh
  9. vim parameter.sh

脚本内容:

  1. #!/bin/bash
  2. echo "$0 $1 $2"

脚本执行:

  1. chmod 755 parameter.sh
  2. ./parameter.sh cls xz
  3. # 结果如下:
  4. # parameter.sh cls xz

2. $

功能描述:获取所有输入参数个数,常用于循环。

  1. # 获取输入参数的个数
  2. vim parameter.sh

脚本内容:

  1. #!/bin/bash
  2. echo "$0 $1 $2"
  3. echo $#

脚本执行:

  1. chmod 755 parameter.sh
  2. ./parameter.sh cls xz
  3. # 结果如下:
  4. # parameter.sh cls xz
  5. # 2

3. $*、$@

  • $:这个变量代表命令行中所有的参数,$把所有的参数看成一个整体。
  • $@:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待。
    1. # 打印输入的所有参数
    2. vim parameter.sh
    脚本内容:
    1. #!/bin/bash
    2. echo "$0 $1 $2"
    3. echo $#
    4. echo $*
    5. echo $@
    脚本执行: ```bash bash parameter.sh 1 2 3

结果如下:

parameter.sh 1 2

3

1 2 3

1 2 3

  1. <a name="sKJpl"></a>
  2. ### 4. $?
  3. 功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。
  4. ```bash
  5. # 判断helloworld.sh脚本是否正确执行
  6. ./helloworld.sh
  7. echo $?
  8. # 结果如下:
  9. # 0

4.4. 特殊命令

  • /dev/null:代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。
  • &0:stdin,标准输入。
  • &1:stdout,标准输出。
  • &2:stderr,标准错误。
  • 2>&1:将标准错误信息转变成标准输出。

    5. 运算符

  1. 语法。
  • “$((运算式))”或“$[运算式]”。
  • expr + , - , *, /, % 加,减,乘,除,取余。

注意:expr运算符间要有空格。

  1. 案例实操。 ```bash

    计算3+2的值

    expr 2 + 3

    结果如下:

    5

计算3-2的值

expr 3 - 2

结果如下:

1

计算(2+3)X4的值

expr一步完成计算

expr expr 2 + 3 * 4

结果如下:

20

采用$[运算式]方式

S=$[(2+3)*4] echo $S

  1. <a name="oRV8Z"></a>
  2. # 6. 条件判断
  3. 1. 语法:[ condition ](注意:condition前后要有空格。)
  4. **注意:条件非空即为true,[ polaris ]返回true,[] 返回false。**
  5. 2. 常用判断条件。
  6. - **两个整数之间比较**
  7. - **=**:字符串比较
  8. - **-lt**:小于(less than)
  9. - **-le**:小于等于(less equal)
  10. - **-eq**:等于(equal)
  11. - **-gt**:大于(greater than)
  12. - **-ge**:大于等于(greater equal)
  13. - **-ne**:不等于(Not equal)
  14. - **按照文件权限进行判断**
  15. - **-r**:有读的权限(read)
  16. - **-w**:有写的权限(write)
  17. - **-x**:有执行的权限(execute)
  18. - **按照文件类型进行判断**
  19. - **-f**:文件存在并且是一个常规的文件(file)
  20. - **-e**:文件存在(existence)
  21. - **-d**:文件存在并是一个目录(directory)
  22. ```bash
  23. # 示例1:23是否大于等于22
  24. [ 23 -ge 22 ]
  25. echo $?
  26. # 结果如下:
  27. # 0
  28. # 示例2:helloworld.sh是否具有写权限
  29. [ -w helloworld.sh ]
  30. echo $?
  31. # 结果如下:
  32. # 0
  33. # 示例3:/home/polaris/cls.txt目录中的文件是否存在
  34. [ -e /home/polaris/cls.txt ]
  35. echo $?
  36. # 结果如下:
  37. # 1
  38. # 示例4:多条件判断(&&表示前一条命令执行成功时,才执行后一条命令,||表示上一条命令执行失败后,才执行下一条命令)
  39. [ condition ] && echo OK || echo notok
  40. # 结果如下:
  41. # OK
  42. [ condition ] && [ ] || echo notok
  43. # 结果如下:
  44. # notok

7. 流程控制(重点)

7.1. if判断

1.语法。

  1. if [ 条件判断式 ];then
  2. 程序
  3. fi
  4. 或者
  5. if [ 条件判断式 ]
  6. then
  7. 程序
  8. fi
  1. **注意:[ 条件判断式 ],中括号和条件判断式之间必须有空格,且if后要有空格。**
  1. # 输入数字,如果是1,则输出I am polaris,如果是2,则输出2 polaris,如果是其它,什么也不输出。
  2. touch if.sh
  3. vim if.sh

脚本内容:

  1. #!/bin/bash
  2. if [ $1 -eq "1" ]
  3. then
  4. echo "I am polaris"
  5. elif [ $1 -eq "2" ]
  6. then
  7. echo "2 polaris"
  8. fi

执行脚本:

  1. chmod 755 if.sh
  2. ./if.sh 1
  3. # 结果如下:
  4. # I am polaris

7.2. case语句

  1. case $变量名 in
  2. "值1"
  3. 如果变量的值等于值1,则执行程序1
  4. ;;
  5. "值2"
  6. 如果变量的值等于值2,则执行程序2
  7. ;;
  8. …省略其他分支…
  9. *)
  10. 如果变量的值都不是以上的值,则执行此程序
  11. ;;
  12. esac

注意:

  1. case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
  2. 双分号“;;”表示命令序列结束,相当于java中的break。
  3. 最后的“*)”表示默认模式,相当于java中的default。
    1. # 输入一个数字,如果是1,则输出polaris,如果是2,则输出cls,如果是其它,输出vo。
    2. touch case.sh
    3. vim case.sh
    脚本内容: ```bash !/bin/bash

case $1 in “1”) echo “polaris” ;;

“2”) echo “cls” ;; *) echo “vo” ;; esac

  1. 执行脚本:
  2. ```bash
  3. chmod 755 case.sh
  4. ./case.sh 1
  5. # 结果如下:
  6. # 1

7.3. for循环

  • 语法1
    1. for (( 初始值;循环控制条件;变量变化 ))
    2. do
    3. 程序
    4. done
    示例:从1加到100。
    1. touch for1.sh
    2. vim for1.sh
    脚本内容: ```bash

    !/bin/bash

s=0 for((i=0;i<=100;i++)) do s=$[$s+$i] done echo $s

  1. 执行脚本:
  2. ```bash
  3. chmod 777 for1.sh
  4. ./for1.sh
  5. # 结果如下:
  6. # “5050”
  • 语法2
    1. for 变量 in 1 2 3
    2. do
    3. 程序
    4. done
    示例:打印所有输入参数。
    1. touch for2.sh
    2. vim for2.sh
    脚本内容: ```bash

    !/bin/bash

    打印数字

for i in $* do echo “ban zhang love $i “ done

  1. 执行脚本:
  2. ```bash
  3. chmod 755 for2.sh
  4. bash for2.sh cls xz bd
  5. # 结果如下:
  6. # ban zhang love cls
  7. # ban zhang love xz
  8. # ban zhang love bd
  • 比较$*和$@区别
  1. $*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n的形式输出所有参数。
    1. touch for.sh
    2. vim for.sh
    脚本内容: ```bash

    !/bin/bash

for i in $* do echo “ban zhang love $i “ done

for j in $@ do
echo “ban zhang love $j” done

  1. 执行脚本:
  2. ```bash
  3. chmod 755 for.sh
  4. bash for.sh cls xz bd
  5. # 结果如下:
  6. # ban zhang love cls
  7. # ban zhang love xz
  8. # ban zhang love bd
  9. # ban zhang love cls
  10. # ban zhang love xz
  11. # ban zhang love bd
  1. 当它们被双引号“”包含时,“$*”会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;“$@”会将各个参数分开,以“$1” “$2”…”$n”的形式输出所有参数。
    1. vim for.sh
    脚本内容: ```bash

    !/bin/bash

for i in “$*”

$*中的所有参数看成是一个整体,所以这个for循环只会循环一次

do echo “ban zhang love $i” done

for j in “$@”

$@中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次

do echo “ban zhang love $j” done

  1. 执行脚本:
  2. ```bash
  3. chmod 755 for.sh
  4. bash for.sh cls xz bd
  5. # 结果如下:
  6. # ban zhang love cls xz bd
  7. # ban zhang love cls
  8. # ban zhang love xz
  9. # ban zhang love bd

7.4. while循环

  1. while [ 条件判断式 ]
  2. do
  3. 程序
  4. done

示例:从1加到100。

  1. touch while.sh
  2. vim while.sh

脚本内容:

  1. #!/bin/bash
  2. s=0
  3. i=1
  4. while [ $i -le 100 ]
  5. do
  6. s=$[$s+$i]
  7. i=$[$i+1]
  8. done
  9. echo $s

执行脚本:

  1. chmod 755 while.sh
  2. ./while.sh
  3. # 结果如下:
  4. # 5050

7.5. 后台处理

  • 方式1:subcommand & ``` 执行现象:
  1. 主进程会继续往下执行,而子进程(subcommand)会在后台启动运行,屏幕上会同时打印主进程和子进程的输出。
  2. CTRL-C杀掉主进程的时候,子进程(subcommand)继续在运行,继续往屏幕打印输出。
  3. 执行命令后关闭终端,主进程和子进程均结束。(原因:当终端结束的时候,子进程会收到SIGHUP信号,缺省的处理方式是杀掉自己,所以子进程也结束了;) ```
  • 方式2:nohup subcommand & ``` nohup作用:
  1. 忽略所有发送给子命令的挂断(SIGHUP,终端结束时,操作系统会发送SIGHUP信号到后台进程)信号。这样所有发给子进程(subcommand)的SIGHUP信号都被忽略,subcommand就不会收到SIGHUP信号。
  2. 重定向子命令的标准输出(stdout)和标准错误(stderr)。表示子进程(subcommand)的标准输出和标准错误被重定向到nohup.out文件;如果没有使用nohup方式,则subcommand的标准输出和标准错误是复用父进程的标准输出和标准错误。

执行现象:

  1. 主进程的输出在屏幕上,子进程(subcommand)的输出在文件nohup.out里。
  2. CTRL-C杀掉主进程的时候,子进程(subcommand)继续在运行,继续往nohup.log里面打印输出。
  3. 执行命令后关闭终端,主进程结束,而子进程继续运行,此时子进程的父进程(pid)变成了进程号1,而不是子进程ID,因为主进程已经死了,操作系统接管了主进程。(原因:当终端结束的时候,子进程会收到SIGHUP信号,由于使用了nohup方式,子进程会忽略SIGHUP信号继续运行。)
  4. 执行命令后先杀死主进程,然后关闭终端,主进程结束,而子进程继续运行(变成了孤儿进程,操作系统自动收集孤儿进程),此时子进程的父进程(pid)变成了进程号1,此时子进程和当前终端已经没有了关系。 ```
  • 方式3:nohup sh subcommand >> logfile 2>&1 & `` 命令说明: 将标准错误信息转变成标准输出(2>&1),将错误信息追加(>>`)到logfile日志文件。

nohup作用: 同上。

执行现象:

  1. 主进程的输出在屏幕上,子进程(subcommand)的输出追加到文件logfile里。
  2. CTRL-C杀掉主进程的时候,子进程(subcommand)继续在运行,继续追加到文件logfile里。
  3. 同上。
  4. 同上。 类似用法:bash

    1. 只输出错误信息到日志文件,丢弃标准输出。

    nohup java -jar app.jar >/dev/null 2>log &

2. 丢弃所有输出,包括错误信息和标准输出。

nohup java -jar app.jar >/dev/null 2>&1 &

  1. <a name="RnSP8"></a>
  2. ## 7.6. 并行处理
  3. 方式1:开启后台调用。
  4. ```bash
  5. echo "开始计算并行模型\n";
  6. nohup sh ${shell_path}/test1.sh ${p1} ${p2} ${p3} >> test1.log 2>&1 &
  7. nohup sh ${shell_path}/test2.sh ${p1} ${p2} ${p3} >> test2.log 2>&1 &
  8. echo "结束计算并行模型\n";

方式2:利用管道和FIFO文件进行多线程后台管理。

  1. #--------初始化 FIFO---begin--------------------
  2. FIFO_FILE="$$.fifo" # 临时生成管道文件
  3. #FIFO_FILE=/tmp/$$.info # 以当前进程号,为临时管道取名
  4. mkfifo ${FIFO_FILE} # 创建临时管道
  5. exec 6<>${FIFO_FILE} # 创建标识为6,可以对管道进行读写
  6. rm ${FIFO_FILE} # 清空管道内容
  7. COUNTER=0 # 计数器
  8. while (( ${COUNTER} < ${PARALLELISM} ))
  9. do
  10. let COUNTER+=1
  11. echo >&6 # 每个echo输出一个回车,为每个进程创建一个占位
  12. done
  13. #--------初始化 FIFO---end----------------------
  14. for(( i = 0; i < ${PARALLELISM}; i++ ))
  15. do
  16. sleep 1
  17. read <&6 # 获取标识为6的占位
  18. {
  19. $JAVA -Xms1024m -Xmx1024m -classpath "$CLASSPATH" *.TrainCompound ${DATA_HOME} ${i}
  20. RETURN_VAL=$?
  21. echo ${RETURN_VAL} >> ${IND_FILE}
  22. echo "多线程生成宽表文件-COMPELED:执行返回值为:${RETURN_VAL}!"`date +"%Y-%m-%d %H:%M:%S"` |F_WLOG
  23. F_EXIT ${RETURN_VAL}
  24. echo >&6 # 当任务执行完后,会释放管道占位,所以补充一个占位
  25. }&
  26. done <&6
  27. #--------等待以上各后台线程结束,并清理 FIFO 文件---begin--------------
  28. wait # 等待所有任务完成
  29. exec 6>&- # 关闭标识为6的管道
  30. #--------等待以上各后台线程结束,并清理 FIFO 文件----end---------------

完整脚本参考:jvm_step3_train_compound.sh

8. 控制台输入

语法:read(选项)(参数)

  • 选项
    • -p:指定读取值时的提示符。
    • -t:指定读取值时等待的时间(秒)。

参数:

  • 变量:指定读取值的变量名。

示例:提示7秒内,读取控制台输入的名称。

  1. touch read.sh
  2. vim read.sh

脚本内容:

  1. #!/bin/bash
  2. read -t 7 -p "Enter your name in 7 seconds " NAME
  3. echo $NAME

执行脚本:

  1. ./read.sh
  2. # 结果如下:
  3. # Enter your name in 7 seconds polaris
  4. polaris

9. 函数

9.1. 系统函数

  • basename

basename [string / pathname] [suffix] (功能描述:basename命令会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。
选项:suffix为后缀,如果suffix被指定了,basename会将pathname或string中的suffix去掉。
示例:截取该/home/polaris/test.txt路径的文件名称。

  1. basename /home/polaris/test.txt
  2. # 结果如下:
  3. # test.txt
  4. basename /home/polaris/test.txt .txt
  5. # 结果如下:
  6. # test
  • dirname

    dirname 文件绝对路径(功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))。
    示例:获取test.txt文件的路径。 ```bash dirname /home/polaris/test.txt

结果如下:

/home/polaris

  1. <a name="I79QR"></a>
  2. ## 9.2. 自定义函数
  3. - **语法**
  4. ```bash
  5. [ function ] funname[()]
  6. {
  7. Action;
  8. [return int;]
  9. }
  10. funname
  • 经验技巧
    • 必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其它语言一样先编译。
    • 函数返回值,只能通过$?系统变量获得,可以显示加:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)。

示例:计算两个输入参数的和。

  1. touch fun.sh
  2. vim fun.sh

脚本内容:

  1. #!/bin/bash
  2. function sum()
  3. {
  4. s=0
  5. s=$[ $1 + $2 ]
  6. echo "$s"
  7. }
  8. read -p "Please input the number1: " n1;
  9. read -p "Please input the number2: " n2;
  10. sum $n1 $n2;

执行脚本:

  1. chmod 755 fun.sh
  2. ./fun.sh
  3. # 结果如下:
  4. # Please input the number1: 2
  5. # Please input the number2: 5
  6. # 7

10. shell运行模式

shell运行模式可以从是否交互、是否登录两个维度进行划分。

  • 交互式模式(interactive shell):shell等待用户的输入,并且执行用户提交的命令。
  • 非交互式模式(non-interactive shell):主要是通过编写shell脚本,再通过系统命令进行调用。
  • 登录模式(login shell):需要用户名、密码登录后才能进入的shell(或者通过”–login”选项生成的shell)。
    • 登录系统时获得的顶层shell,无论是通过本地终端登录,还是通过网络ssh登录。这种情况下获得的login shell是一个交互式shell。
    • 在终端下使用—login选项调用bash,可以获得一个交互式login shell。
    • 在脚本中使用—login选项调用bash(比如在shell脚本第一行做如下指定:#!/bin/bash —login),此时得到一个非交互式的login shell。
    • 使用”su -“切换到指定用户时,获得此用户的login shell。如果不使用”-“,则获得non-login shell
  • 非登录模式(non-login shell):不需要输入用户名和密码即可打开的Shell,例如:直接命令“bash”就是打开一个新的非登录shell,在Gnome或KDE中打开一个“终端”(terminal)窗口程序也是一个非登录shell。
    • 新启动一个shell进程,如运行bash,则属于:non-login + interactive。
    • 执行脚本,如bash script.sh,则属于:non-login + non-interactive。
    • 运行头部有如#!/usr/bin/env bash的可执行文件,如./executable,则属于:non-login + non-interactive。
    • 远程执行脚本,如ssh user@remote script.sh,则属于:non-login + non-interactive。
    • 远程执行脚本,同时请求控制台,如ssh user@remote -t ‘echo $PWD’,则属于:non-login + interactive。
    • 用Ansible在目标机器上远程执行shell脚本时,也是non-login shell,因为Ansible是基于ssh的。

image.png
注意:non-login shell与login shell的主要区别在于它们启动时会读取不同的配置文件,从而导致环境不一样。,login shell启动时会加载/etc/profile,~/.bash_profile(实际为~/.bash_profile、~/.bash_login、~/.profile三个配置文件中第一个找到的可读的文件),~/.bashrc。non-login shell启动时会加载~/.bashrc。通常我们要定制一些配置时,将配置写在~/.bashrc或者/etc/profile.d/*.sh,这样可以保证login shell和交互式non-login shell得到相同的配置。

11. shell工具(重点)

1. cut

cut的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。

  • 语法:cut [选项参数] filename

说明:默认分隔符是制表符。

  • 参数说明
    • -f:列号,提取第几列。
    • -d:分隔符,按照指定分隔符分割列。
  • 案例实操
    1. touch cut.txt
    2. vim cut.txt
    内容:
    1. dong shen
    2. guan zhen
    3. wo wo
    4. lai lai
    5. le le
    示例1:切割cut.txt第1列。 ```bash cut -d “ “ -f 1 cut.txt

结果如下:

dong

guan

wo

lai

le

  1. 示例2:切割cut.txt23列。
  2. ```bash
  3. cut -d " " -f 2,3 cut.txt
  4. # 结果如下:
  5. # shen
  6. # zhen
  7. # wo
  8. # lai
  9. # le

示例3:在cut.txt文件中切割出guan。

  1. cat cut.txt | grep "guan" | cut -d " " -f 1
  2. # 结果如下:
  3. # guan

示例4:选取系统PATH变量值,第2个“:”开始后的所有路径。

  1. echo $PATH
  2. # 结果如下:
  3. # /usr/lib64/*/bin:/usr/local/bin:/bin:/usr/bin:...:/home/polaris/bin
  4. echo $PATH | cut -d: -f 2-
  5. # 结果如下:
  6. # /usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/polaris/bin

示例5:切割ifconfig 后打印的IP地址。

  1. ifconfig eth0 | grep "inet addr" | cut -d: -f 2 | cut -d" " -f1
  2. 192.168.56.101

2. sed

sed是一种流编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

  • 语法:sed [选项参数] ‘command’ filename
  • 参数说明
    • -e:直接在指令列模式上进行sed的动作编辑。
  • 命令功能描述
    • a:新增,a的后面可以接字串,在下一行出现。
    • d:删除。
    • s:查找并替换。
  • 案例实操
    1. touch sed.txt
    2. vim sed.txt
    内容: ```bash dong shen guan zhen wo wo lai lai

le le

  1. 示例1:将“mei nv”这个单词插入到sed.txt第二行下,打印。
  2. ```bash
  3. sed '2a mei nv' sed.txt
  4. # 结果如下:
  5. # dong shen
  6. # guan zhen
  7. # mei nv
  8. # wo wo
  9. # lai lai
  10. #
  11. # le le
  12. cat sed.txt
  13. # 结果如下:
  14. # dong shen
  15. # guan zhen
  16. # wo wo
  17. # lai lai
  18. #
  19. # le le

注意:文件并没有改变
示例2:删除sed.txt文件所有包含wo的行。

  1. sed '/wo/d' sed.txt
  2. # 结果如下:
  3. # dong shen
  4. # guan zhen
  5. # lai lai
  6. #
  7. # le le

示例3:将sed.txt文件中wo替换为ni。

  1. sed 's/wo/ni/g' sed.txt
  2. # 结果如下:
  3. # dong shen
  4. # guan zhen
  5. # ni ni
  6. # lai lai
  7. #
  8. # le le

注意:‘g’表示global,全部替换。
示例4:将sed.txt文件中的第二行删除并将wo替换为ni。

  1. sed -e '2d' -e 's/wo/ni/g' sed.txt
  2. # 结果如下:
  3. # dong shen
  4. # ni ni
  5. # lai lai
  6. #
  7. # le le

3. awk

awk是一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。
语法:awk [选项参数] ‘pattern1{action1} pattern2{action2}…’ filename

  • 选项
    • pattern:表示AWK在数据中查找的内容,就是匹配模式。
    • action:在找到匹配内容时所执行的一系列命令。
  • 参数说明
    • -F:指定输入文件折分隔符。
    • -v:赋值一个用户定义变量。
  • 案例实操
    1. sudo cp /etc/passwd ./
    示例1:搜索passwd文件以root关键字开头的所有行,并输出该行的第7列。 ```bash awk -F: ‘/^root/{print $7}’ passwd

结果如下:

/bin/bash

  1. 示例2:搜索passwd文件以root关键字开头的所有行,并输出该行的第1列和第7列,中间以“,”号分割。
  2. ```bash
  3. awk -F: '/^root/{print $1","$7}' passwd
  4. # 结果如下:
  5. # root,/bin/bash

注意:只有匹配了pattern的行才会执行action。
示例3:只显示/etc/passwd的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell在最后一行添加”dahaige,/bin/zuishuai”。

  1. awk -F : 'BEGIN{print "user, shell"} {print $1","$7} END{print "dahaige,/bin/zuishuai"}' passwd
  2. # 结果如下:
  3. # user, shell
  4. # root,/bin/bash
  5. # bin,/sbin/nologin
  6. # ...
  7. # atguigu,/bin/bash
  8. # dahaige,/bin/zuishuai

注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。
示例4:将passwd文件中的用户id增加数值1并输出。

  1. awk -v i=1 -F: '{print $3+i}' passwd
  2. # 结果如下:
  3. # 1
  4. # 2
  5. # 3
  6. # 4
  • awk的内置变量 | 变量 | 说明 | | —- | —- | | FILENAME | 文件名 | | NR | 已读的记录数 | | NF | 浏览记录的域的个数(切割后,列的个数) |

示例5:统计passwd文件名,每行的行号,每行的列数。

  1. awk -F: '{print "filename:" FILENAME ", linenumber:" NR ",columns:" NF}' passwd
  2. # 结果如下:
  3. # filename:passwd, linenumber:1,columns:7
  4. # filename:passwd, linenumber:2,columns:7
  5. # filename:passwd, linenumber:3,columns:7

示例6:切割IP。

  1. ifconfig eth0 | grep "inet addr" | awk -F: '{print $2}' | awk -F " " '{print $1}'
  2. # 结果如下:
  3. # 192.168.56.101

示例7:查询sed.txt中空行所在的行号。

  1. awk '/^$/{print NR}' sed.txt
  2. # 结果如下:
  3. # 5

4. sort

sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出。

  • 语法:sort(选项)(参数)
  • 选项
    • -n:依照数值的大小排序。
    • -r:以相反的顺序来排序。
    • -t:设置排序时所用的分隔字符。
    • -k:指定需要排序的列。
  • 参数:指定待排序的文件列表。
  • 案例实操
    1. touch sort.sh
    2. vim sort.sh
    内容:
    1. bb:40:5.4
    2. bd:20:4.2
    3. xz:50:2.3
    4. cls:10:3.5
    5. ss:30:1.6
    示例:按照“:”分割后的第三列倒序排序。 ```bash sort -t : -nrk 3 sort.sh

结果如下:

bb:40:5.4

bd:20:4.2

cls:10:3.5

xz:50:2.3

ss:30:1.6

  1. <a name="TLRRg"></a>
  2. # 12. 企业真实面试题(重点)
  3. <a name="7EbfK"></a>
  4. ## 1. 京东
  5. - 问题1:使用Linux命令查询file1中空行所在的行号。
  6. ```bash
  7. awk '/^$/{print NR}' sed.txt
  8. # 结果如下:
  9. # 5
  • 问题2:文件polaris.txt内容如下,使用Linux命令计算第2列的和并输出。
    1. 张三 40
    2. 李四 50
    3. 王五 60
    解答: ```bash cat polaris.txt | awk -F “ “ ‘{sum+=$2} END{print sum}’

结果如下:

150

  1. <a name="xTfSD"></a>
  2. ## 2. 搜狐&和讯网
  3. Shell脚本里如何检查一个文件是否存在?如果不存在该如何处理?
  4. ```bash
  5. #!/bin/bash
  6. if [ -f polaris.txt ]; then
  7. echo "文件存在!"
  8. else
  9. echo "文件不存在!"
  10. fi

3. 新浪

用shell写一个脚本,对文本中无序的一列数字排序。

  1. vi polaris.txt

内容:

  1. 9
  2. 8
  3. 7
  4. 6
  5. 5
  6. 4
  7. 3
  8. 2
  9. 10
  10. 1

解答:

  1. sort -n polaris.txt|awk '{a+=$0;print $0}END{print "SUM="a}'

结果如下:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. SUM=55

3. 金和网络

请用shell脚本写出查找当前文件夹(/home)下所有的文本文件内容中包含有字符“shen”的文件名称。

  1. grep -r "shen" /home | cut -d ":" -f 1
  2. # 结果如下:
  3. # /home/polaris/datas/sed.txt
  4. # /home/polaris/datas/cut.txt

参考

简书:Shell的后台运行(&)与nohup
https://www.jianshu.com/p/747e0d5021a2
博客园:ssh连接远程主机执行脚本的环境变量问题
https://www.cnblogs.com/asia90li/p/6437677.html
博客园:ssh远程执行命令
https://www.cnblogs.com/youngerger/p/9104144.html