shell脚本语法

一、显示消息

使用 echo 命令来显示消息

$ echo This is a test
This is a test

$ echo "Let's see if this'll work"
Let's see if this'll work

echo 输出不换行

echo 命令的 -n 参数表示输出文本字符后不换行

$ cat test.sh
#!/bin/sh
echo -n "The time and date are: "
date

$ ./test.sh
The time and date are: Thu Oct 17 11:54:49 CST 2019

echo 输出特殊转义字符

echo 使用 -e 参数来输出特殊的转义字符,特殊的转义字符包括 制表符(\t)、换行符(\n)等。

$ echo -e "hello\tworld"
hello	world

$ echo -e "hello\nworld"
hello
world

二、变量

默认shell环境变量

变量 描述
HOME 当前用户的主目录
IFS shell用来将文本字符串分割成字段的一系列字符
PATH shell查找命令的目录列表,由冒号分隔
PS1 shell命令行界面的主提示符
PS2 shell命令行界面的次提示符
PWD 当前工作目录
REPLY read命令的默认变量
UID 当前用户的真实用户ID(数字形式)

用户变量

等号(=)两边不能有空格

var1=10  
var2=-57  
var3=testing  
var4="still more testing"  

访问变量

变量名前加 $ 符号来访问变量, $var 或者 ${var}

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin

$ echo ${USER}
heyb

$ echo $var1
10

$ echo ${var3}
testing

命令替换

命令替换是从命令输出中提取信息,然后赋值给变量

有两种方法可以把命令输出赋值给变量,等号(=)两边不能空格

  1. 反引号字符 (`)
  2. $() 格式
  • 反引号字符 (`) 格式
$ testing=`date`
$ echo $testing
Thu Oct 17 14:22:35 CST 2019
  • $() 格式
$ testing=$(date)
$ echo $testing
Thu Oct 17 14:22:35 CST 2019

三、输出和输入重定向

输出重定向

command > outputfile

最基本的重定向将命令的输出发送到一个文件中,用大于号(>)来完成该功能。

如果该文件不存在,则新建该文件;如果该文件已经存在,则会覆盖文件的数据。

输出重定向向文件中追加数据用双大于号 ( >> )。

$ date > testfile
$ cat testfile
Thu Oct 17 14:59:19 CST 2019

$ who >> testfile
$ cat testfile
Thu Oct 17 14:59:19 CST 2019
heyb     pts/0        2019-10-08 19:43 (:0)

输入重定向

输入重定向将文件的内容重定向到命令,输入重定向符号是小于号 ( < )

command < inputfile

$ wc < testfile
2 11 73

wc命令可以对文件中的文本数据进行计数,输出的3个默认值分别表示:文本的行数、文本的词数、文本的字节数

内联输入重定向

内联输入重定向符号是远小于号(<<)。除了这个符号,你必须指定一个标记符号来划分输
入数据的开始和结尾。任何字符串都可作为标记符号,但在数据的开始和结尾标记符号必须一致。

command << EOF
data
EOF
$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
3 9 42

四、数学运算

expr命令

$ expr 1 + 5
6

$ expr 5 * 2
expr: syntax error

$ expr 5 \* 2
10

使用方括号

将一个数学运算结果赋给某个变量时,可以用美元符号和方括号( $[ operation ] )将数学表达式围起来

$ var1=$[1 + 5]
$ echo $var1
6

$ var2=$[$var1 * 2]
$ echo $var2
12

浮点数运算

浮点数运算用 bc 命令

$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
12 * 5.4
64.8

3.156 * (3 + 5)
25.248
quit

五、if 语句

if-then 语句

if command; then
    commands
fi

if 语句会执行 if 后面的那个命令。
如果该命令的退出状态码是 0(该命令成功运行),if条件成立,位于 then 部分的命令就会被执行。
如果该命令的退出状态码是其他值, 则if条件不成立,then部分的命令就不会被执行。

$ cat test.sh
#!/bin/sh

if pwd; then
    echo "It worked."
fi

$ ./test.sh
/home/heyb
It worked

if-then-else 语句

if command; then
    commands
else
    commands
fi
$ cat test.sh
#!/bin/sh

testuser=NoSuchUser

if grep $testuser /etc/passwd; then
    echo "The bash files for user $testuser are:"
    ls -a /home/$testuser/.b*
    echo
else
    echo "The user $testuser does not exist on this system."
    echo
fi

$ ./test.sh
The user NoSuchUser does not exist on this system.

test 命令

test 命令提供了在 if-then 语句中测试不同条件的途径。如果 test 命令中列出的条件成立, test 命令就会退出并返回退出状态码 0 。这样 if-then 语句就与其他编程语言中的 if-then 语句以类似的方式工作了。如果条件不成立, test 命令就会退出并返回非零的退出状态码,这使得 if-then 语句不会再被执行。

test 命令的格式非常简单。

test condition

当用在 if-then 语句中时, test 命令看起来是这样的

if test condition;then
    commands
fi

如果不写 test 命令的 condition 部分,它会以非零的退出状态码退出,并执行 else 语句块。

$ cat test.sh
#!/bin/bash

if test; then
    echo "No expression returns a True"
else
    echo "No expression returns a False"
fi

$ ./test.sh
No expression returns a False

bash shell提供了另一种条件测试方法,无需在 if-then 语句中声明 test 命令。

if [ condition ]; then
    commands
fi

方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。

test 命令可以判断三类条件

  1. 数值比较
  2. 字符串比较
  3. 文件比较

数值比较

不能在 test 命令中使用浮点值,test 命令只能处理整数

比较 描述
n1 -eq n2 检查 n1 是否与 n2 相等
n1 -ge n2 检查 n1 是否大于或等于 n2
n1 -gt n2 检查 n1 是否大于 n2
n1 -le n2 检查 n1 是否小于或等于 n2
n1 -lt n2 检查 n1 是否小于 n2
n1 -ne n2 检查 n1 是否不等于 n2
$ cat test.sh
#!/bin/bash

value1=10
value2=11

if [ $value1 -gt 5 ]
then
    echo "The test value
fi

if [ $value1 -eq $value2 ]
then
    echo "The values are
else
    echo "The values are
fi

$ ./test.sh
The test value 10 is greater than 5
The values are different

字符串比较

比较 描述
str1 = str2 检查 str1 是否和 str2 相同
str1 != str2 检查 str1 是否和 str2 不同
str1 < str2 检查 str1 是否比 str2 小
str1 > str2 检查 str1 是否比 str2 大
-n str1 检查 str1 的长度是否非0
-z str1 检查 str1 的长度是否为0

在测试条件中使用大于或小于功能时,大于号和小于号必须转义,否则shell会把他们当作重定向符号

$ cat test.sh
#!/bin/bash
# testing string equality

testuser=heyb
#
if [ $USER = $testuser ]
then
echo "Welcome $testuser"
fi

$ ./test.sh
Welcome heyb
$ cat test.sh
#!/bin/bash
# mis-using string comparisons

val1=baseball
val2=hockey

if [ $val1 \> $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi

$ ./test.sh
baseball is less than hockey

文件比较

比较 描述
-d file 检查 file 是否存在并是一个目录
-e file 检查 file 是否存在
-f file 检查 file 是否存在并是一个文件
-r file 检查 file 是否存在并可读
-s file 检查 file 是否存在并非空
-w file 检查 file 是否存在并可写
-x file 检查 file 是否存在并可执行
-O file 检查 file 是否存在并属当前用户所有
-G file 检查 file 是否存在并且默认组与当前用户相同
file1 -nt file2 检查 file1 是否比 file2 新
file1 -ot file2 检查 file1 是否比 file2 旧

复合条件测试

if-then 语句允许你使用布尔逻辑来组合测试,有两种布尔运算符可用

[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]

第一种布尔运算使用 AND 布尔运算符来组合两个条件。要让 then 部分的命令执行,两个条件都必须满足

第二种布尔运算使用 OR 布尔运算符来组合两个条件。如果任意条件为 TRUE , then 部分的命令就会执行

$ cat test.sh
#!/bin/bash
# testing compound comparisons

if [ -d $HOME ] && [ -w $HOME/testing ]
then
    echo "The file exists and you can write to it"
else
    echo "I cannot write to the file"
fi

$ ./test.sh
I cannot write to the file

$ touch $HOME/testing

$ ./test.sh
The file exists and you can write to it

if-then 的高级特性

bash shell提供了两项可在 if-then 语句中使用的高级特性:

  1. 用于数学表达式的双括号 (( ))
  2. 用于高级字符串处理功能的双方括号 [[ ]]

使用双括号

双括号命令允许你在比较过程中使用高级数学表达式。 test 命令只能在比较中使用简单的算术操作,双括号命令提供了更多的数学符号

双括号命令的格式如下:

(( expression ))

符号 描述
val++ 后增
val-- 后减
++val 先增
--val 先减
! 逻辑求反
~ 位求反
** 幂运算
<< 左位移

| 右位移
& | 位布尔和
| | 位布尔或
&& | 逻辑和
|| | 逻辑或

$ cat test.sh
#!/bin/bash
# using double parenthesis

val1=10

if (( $val1 ** 2 > 90 ))
then
    (( val2 = $val1 ** 2 ))
    echo "The square of $val1 is $val2"
fi

$ ./test.sh
The square of 10 is 100

可以在 if 语句中用双括号命令,也可以在脚本中的普通命令里使用来赋值

不需要将双括号中表达式里的大于号转义,这是双括号命令提供的另一个高级特性

使用双方括号

双方括号命令提供了针对字符串比较的高级特性,双方括号命令的格式如下:

[[ expression ]]

双方括号里的 expression 使用了 test 命令中采用的标准字符串比较。但它提供了 test 命令未提供的另一个特性——模式匹配(pattern matching)。

在模式匹配中,可以定义一个正则表达式来匹配字符串值

$ cat test.sh
#!/bin/bash
# using pattern matching

if [[ $USER == h* ]]
then
    echo "Hello $USER"
else
    echo "Sorry, I do not know you"
fi

$ ./test.sh
Hello heyb

在上面的脚本中,我们使用了双等号( == )。双等号将右边的字符串( r* )视为一个模式,并应用模式匹配规则。双方括号命令 $USER 环境变量进行匹配,看它是否以字母 h 开头。如果是的话,比较通过,shell会执行 then 部分的命令。

六、case语句

case语句的格式:

case variable in
    pattern1 | pattern2)
        commands1
    ;;
    pattern3) 
        commands2
    ;;
    *) default 
        commands
    ;;
esac

case 语句示例:

$ cat test.sh
#!/bin/bash
# using the case command

case $USER in
    heyb | barbara)
        echo "Welcome, $USER"
        echo "Please enjoy your visit"
    ;;
    testing)
        echo "Special testing account"
        ;;
    jessica)
        echo "Do not forget to log off when you're done"
    ;;
    *)
        echo "Sorry, you are not allowed here"
    ;;
esac

$ ./test.sh
Welcome, heyb
Please enjoy your visit

七、for语句

for语句的基本格式:

for var in list
do
    commands
done

list提供迭代需要的一系列值。

第一次迭代会使用list中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。

for语句中list列表的值可以通过以下几种方式获取:

  • 直接指定list中的值
$ cat test.sh
#!/bin/bash
# basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
    echo The next state is $test
done

$ ./test.sh
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado
  • 从变量中读取list的值
$ cat test.sh
#!/bin/bash
# using a variable to hold the list 13
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"

for state in $list
do
    echo "Have you ever visited $state?"
done

$ ./test.sh
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?

$list 变量包含了用于迭代的标准文本值列表。注意,代码还是用了另一个赋值语句向 $list变量包含的已有列表中添加(或者说是拼接)了一个值。这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法。

  • 从命令读取
$ cat test.sh
#!/bin/bash
# reading values from a file

file="states"
for state in $(cat $file)
do
    echo "Visit beautiful $state"
done

$ cat states
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia

$ ./test.sh
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia

字段分隔符

bash shell有一个特殊的环境变量 IFS ,叫作内部字段分隔符(internal field separator)。

IFS 环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

  1. 空格
  2. 制表符
  3. 换行符

IFS 的值设置为换行符:

IFS=$'\n'

IFS 的值设为冒号:

IFS=:

IFS 的值设为多个字符:

IFS=$'\n':;"

这个赋值会将换行符(\n)、冒号(:)、分号(;)和双引号(")作为字段分隔符

  • 用通配符读取目录作为list的值
$ cat test.sh
#!/bin/bash
# iterate through all the files in a directory

for file in /home/heyb/test/*
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    elif [ -f "$file" ]
    then
        echo "$file is a file"
    fi
done

$ ./test.sh
/home/rich/heyb/dir1 is a directory
/home/rich/heyb/myprog.c is a file
/home/rich/heyb/myprog is a file
/home/rich/heyb/myscript is a file
/home/rich/heyb/newdir is a directory
/home/rich/heyb/newfile is a file
/home/rich/heyb/newfile2 is a file
/home/rich/heyb/testdir is a directory
/home/rich/heyb/testing is a file
/home/rich/heyb/testprog is a file
/home/rich/heyb/testprog.c is a file

C 语言风格的 for 命令

bash shell中C语言风格的 for 循环的基本格式:

for (( variable assignment ; condition ; iteration process ))

for (( a = 1; a < 10; a++ ))

$ cat test.sh
#!/bin/bash
# testing the C-style for loop

for (( i=1; i <= 10; i++ ))
do
    echo "The next number is $i"
done

$ ./test.sh
The next number is 1
The next number is 2
The next number is 3
The next number is 4
The next number is 5
The next number is 6
The next number is 7
The next number is 8
The next number is 9
The next number is 10

八、while语句

while 的基本格式:

while test command
do
    other commands
done

最常见的 test command 的用法是用方括号来检查循环命令中用到的shell变量的值

$ cat test.sh
#!/bin/bash
# while command test

var1=10

while [ $var1 -gt 0 ]
do
    echo $var1
    var1=$[ $var1 - 1 ]
done

$ ./test.sh
10
9
8
7
6
5
4
3
2
1

控制循环

break 命令

break 跳出当前循环

continue 命令

continue 命令可以提前中止某次循环,但并不会完全终止整个循环

九、命令行参数和特殊参数变量

位置参数变量是标准的数字: $0 是程序名, $1 是第一个参数, $2 是第二个参数,依次类推,直到第九个参数 $9

在第9个变量之后,必须在变量数字周围加上花括号,比如 ${10}表示第十个参数

basename 命令返回不包含路径的脚本名

$ basename /home/heyb/test/test.sh
test.sh
参数名 含义
$0 脚本程序名
$1 ~ $9 第一个命令行参数 ~ 第九个命令行参数
$ 第十个命令行参数
$? 命令执行的返回值,成功返回0,失败返回非零值
$# 命令行参数的个数,不包括程序名
$* 将命令行上提供的所有参数当作一个单词保存,将所有参数当成单个参数
$@ 将命令行上提供的所有参数当作同一字符串中的多个独立的单词,单独处理每个参数

移动变量

shift 命令会根据参数相对位置来移动命令行参数
shift 向左边依次移动命令行参数,移出去的参数的值将被丢弃,不能恢复

$ cat test.sh
#!/bin/bash
# demonstrating the shift command

echo
count=1
while [ -n "$1" ]
do
    echo "Parameter #$count = $1"
    count=$[ $count + 1 ]
    shift
done

$ ./test.sh rich barbara katie jessica
Parameter #1 = rich
Parameter #2 = barbara
Parameter #3 = katie
Parameter #4 = jessica

十、获得用户输入

read 命令获取用户输入

read 命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后, read 命令会将数据放进一个变量

$ cat test.sh
#!/bin/bash
# testing the read command

echo -n "Enter your name: "
read name
echo "Hello $name, welcome to my program. "

$ ./test.sh
Enter your name: heyb
Hello heyb, welcome to my program.

read 命令包含 -p 选项,允许直接在 read 命令行指定提示符

$ read -p "Please enter your age: " age
Please enter your age: 10

用 read 命令来读取文件中保存的数据

每次调用 read 命令,它都会从文件中读取一行文本。当文件中没有内容时, read 命令会退出并返回非零退出状态码

$ cat test.sh
#!/bin/bash
# reading data from a file

count=1
cat test | while read line
do
    echo "Line $count: $line"
    count=$[ $count + 1]
done
echo "Finished processing the file"

$ cat test
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Romeo?

$ ./test.sh
Line 1: The quick brown dog jumps over the lazy fox.
Line 2: This is a test, this is only a test.
Line 3: O Romeo, Romeo! Wherefore art thou Romeo?
Finished processing the file
$

从文件中读取数据,一种比较常见的写法是利用输入重定向来实现:

$ cat test.sh
#!/bin/bash
# reading data from a file

count=1
while read line
do
    echo "Line $count: $line"
    count=$[ $count + 1]
done < test
echo "Finished processing the file"

$ cat test
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Romeo?

$ ./test.sh
Line 1: The quick brown dog jumps over the lazy fox.
Line 2: This is a test, this is only a test.
Line 3: O Romeo, Romeo! Wherefore art thou Romeo?
Finished processing the file

十一、重定向

符号 含义

| 输出重定向
2> | 错误重定向
&> | 错误数据和标准输出同时重定向到一个文件中
&2 | 把输出重定向到错误输出,主要用来人为生成错误消息

$ cat test.sh
#!/bin/bash
# testing STDERR messages

echo "This is an error" >&2
echo "This is normal output"

$ ./test.sh 2> test
This is normal output

$ cat test9
This is an error

十二、函数

创建函数

bash shell中有2种风格的格式来创建函数:

  1. 采用关键字 function,后跟函数名 (function 和 函数名之间要有空格
function name {  
    commands  
}  
  1. 在函数名的后面加上空括号,表明定义的是一个函数
name() {  
    commands  
}  

函数调用

调用函数只要指定函数名就可以了

函数定义必须在函数调用之前

函数返回值

默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量 $? 来确定函数的退出状态码

也可以通过return来给函数返回一个返回值

函数的返回值也可以赋值给一个变量

ret=$(fun_name)

这个命令会将 fun_name 函数的输出赋给 result 变量,然后通过 ${ret} 来获取函数的返回值

$ cat test.sh
#!/bin/sh

function func_test {
    echo "This is function test..."

    return 2
}

func_test

echo "The return value is " $?

$ ./test.sh
This is function test...
The return value is  2
$ cat test.sh
#!/bin/bash
# using the echo to return a value

function func_test {
    echo "100"
}

result=$(func_test)                    
echo "the result is $result"

$ ./test.sh
the result is 100

向函数传递参数

函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0 变量中定义,
函数命令行上的任何参数都会通过 $1、$2 等定义。也可以用特殊变量 $# 来判断传给函数的参数数目

在脚本中调用函数时,必须将参数和函数放在同一行,像这样:

test_fun param1 param2

$ cat test.sh
#!/bin/sh

function test_fun {
    echo "function params test..."

    echo "first param is " $1
    echo "second param is " $2
}

test_fun hello world

$ ./test.sh hello world
function params test...
first param is  hello
second param is  world

在函数中处理变量

函数使用两种类型的变量:

  1. 全局变量
  2. 局部变量

全局变量是在shell脚本中任何地方都有效的变量。
如果你在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。
类似地,如果你在函数内定义了一个全局变量,可以在脚本的主体部分读取它的值。

不需要在函数中使用全局变量时,函数内部使用的任何变量都可以被声明成局部变量。要实现这
一点,只要在变量声明的前面加上 local 关键字就可以了

local temp

十三、sed编辑器

sed编辑器被叫做流式编辑器。

sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个命令文本文件中

sed编辑器执行流程如下:

  1. 一次从输入中读取一行数据
  2. 根据所提供的编辑器命令匹配数据
  3. 按照命令修改流中的数据
  4. 将新的数据输出到 STDOUT

sed命令的基本语法:

sed [option] commands [commands-file]

sed命令选项:

选项 说明
-e commands 在处理数据时,执行 commands 指定的一条或多条命令
-f commands-file 在处理数据时,从 commands-file 的命令文件中读取命令
-n 在处理数据时,不产生输出

在命令行使用sed编辑器命令

$ echo "This is a test" | sed 's/test/big test/'
this is a big test
$ cat data.txt
this is a test
this is a test
this is a test
this is a test
this is a test

$ sed 's/test/big test/' data.txt
this is a big test
this is a big test
this is a big test
this is a big test
this is a big test

在命令行使用多个sed编辑器命令

两个命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格

$ cat data.txt
this is a test
this is a test
this is a test
this is a test
this is a test

$ sed -e 's/a/two/; s/test/big test/' data.txt
this is two big test
this is two big test
this is two big test
this is two big test
this is two big test

从文件中读取sed编辑器命令

$ cat commands.sed 
s/a/two/
s/test/big test/

$ sed -f commands.sed data.txt 
this is two big test
this is two big test
this is two big test
this is two big test
this is two big test

commands.sed命令文件中的每条命令后面不用放分号,sed编辑器知道每行都是一条单独的命令

sed命令

命令 说明
s 替换
d 删除
i 插入
a 追加
c 修改
y 转换

替换命令

替换命令用 s 来表示,替换命令的格式如下:

s/a/b/ 数据中的 a 将被 b 替换

替换标记

替换标记在替换命令字符串之后设置

s/pattern/replacement/flags

有4种可用的替换标记:

  1. 数字,表明新文本将替换第几处模式匹配的地方;
  2. g ,表明新文本将会替换所有匹配的文本;
  3. p ,表明原先行的内容要打印出来;
  4. w file ,将替换的结果写到文件中

数字替换标记可以指定sed编辑器替换第几处模式匹配的地方

$ cat data.txt
This is a test of the test script.
This is the second test of the test script.

$ sed 's/test/trial/2' data.txt
This is a test of the trial script.
This is the second test of the trial script.

替换标记指定为 2 的结果就是:sed编辑器只替换每行中第二次出现的匹配模式

g 替换标记可以替换文本中匹配模式所匹配到的每个地方

$ cat data.txt
This is a test of the test script.
This is the second test of the test script.

$ sed 's/test/trial/g' data.txt
This is a trial of the trial script.
This is the second trial of the trial script.

p 替换标记会打印替换命令中指定的模式匹配的行。这通常会和 sed 的 -n 选项一起使用。

$ cat data.txt
This is a test line.
This is a different line.

$ sed -n 's/test/trial/p' data.txt
This is a trial line.

-n 选项将禁止sed编辑器输出。但 p 替换标记会输出修改过的行。将二者配合使用的效果就是只输出被替换命令修改过的行

w 替换标记会产生同样的输出,不过会将输出保存到指定文件中

$ cat data.txt
This is a test line.
This is a different line.

$ sed 's/test/trial/w test.txt' data.txt
This is a trial line.
This is a different line.

$ cat test.txt
This is a trial line.

替换特殊字符
替换特殊字符需要使用反斜线转义字符 ()

$ sed 's/\/bin\/bash/\/bin\/sh/' /etc/passwd

sed编辑器允许使用其他字符来作为替换命令中的字符串分隔符:

$ sed 's!/bin/bash!/bin/csh!' /etc/passwd

在这个例子中,感叹号被用作字符串分隔符

使用行寻址

默认情况下,在sed编辑器中使用的命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须用行寻址

在sed编辑器中有两种形式的行寻址:

  1. 以数字形式表示行区间
  2. 用文本模式来过滤出指定的行

两种形式都使用相同的格式来指定行地址:

[address] command

也可以将特定地址的多个命令分组:

address {
    command1
    command2
    command3
}
  • 数字方式的行寻址

在命令中指定的行地址可以是单个行号,也可以是用起始行号、逗号以及结尾行号指定的一定区间范围内的行

sed编辑器只修改第二行的数据。

$ cat data.txt
this is a test
this is a test
this is a test
this is a test
this is a test

$ sed '2s/test/big test/' data.txt
this is a test
this is a big test
this is a test
this is a test
this is a test

只修改第二行和第三行

$ sed '2,3s/test/big test/' data.txt
this is a test
this is a big test
this is a big test
this is a test
this is a test

修改从第二行开始的所有行

$ sed '2,$s/test/big test/' data.txt
this is a test
this is a big test
this is a big test
this is a big test
this is a big test
  • 使用文本模式过滤器来进行行寻址

格式如下:

/pattern/command

必须用正斜线将要指定的 pattern 封起来。sed编辑器会将该命令作用到包含指定文本模式的行上

$ grep Samantha /etc/passwd
Samantha:x:502:502::/home/Samantha:/bin/bash

$ sed '/Samantha/s/bash/csh/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[...]
Christine:x:501:501:Christine B:/home/Christine:/bin/bash
Samantha:x:502:502::/home/Samantha:/bin/csh
Timothy:x:503:503::/home/Timothy:/bin/bash
  • 使用组合命令
$ sed '2{
> s/a/two/
> s/test/big test/
> }' data.txt
this is a test
this is two big test
this is a test
this is a test
this is a test

$ sed '2,3{
> s/a/two/
> s/test/big test/
> }' data.txt
this is a test
this is two big test
this is two big test
this is a test
this is a test

删除命令

删除命令使用 d , 它会删除匹配模式匹配到的所有行

$ cat data.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

$ sed '3d' data.txt
This is line number 1.
This is line number 2.
This is line number 4.

$ sed '3,$d' data.txt
This is line number 1.
This is line number 2.

插入和追加命令

  1. 插入( insert )命令( i )会在指定行前增加一个新行;
  2. 附加( append )命令( a )会在指定行后增加一个新行。

格式如下:

sed '[address] command\new-line'

$ echo "test line 2" | sed 'i\test line 1'
test line 1
test line 2

$ echo "test line 2" | sed 'a\test line 1'
test line 2
test line 1

修改行

修改( change )命令使用 c, 它允许修改数据流中整行文本的内容

$ cat data.txt
this is line 1.
this is line 2.
this is line 3.
this is line 4.

$ sed '2c\this is chang line.' data.txt
this is line 1.
this is change line.
this is line 3.
this is line 4.

转换命令

转换( transform )命令( y )是唯一可以处理单个字符的sed编辑器命令

转换命令格式如下:

[address]y/inchars/outchars/

转换命令会对 inchars 和 outchars 值进行一对一的映射。 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符。这个映射过程会一直持续到处理完指定字符。如果 inchars 和 outchars 的长度不同,则sed编辑器会产生一条错误消息

$ cat data.txt
this is line 1.
this is line 2.
this is line 3.
this is line 4.

$ sed 'y/123/789/' data.txt
this is line 7.
this is line 8.
this is line 9.
this is line 4.

十四、awk程序

awk程序脚本用一对花括号来定义,必须将脚本命令放到两个花括号 { } 中,还必须把带花括号的命令放在单引号中

awk命令格式:

awk option '{commands}' data-file

$ cat data.txt
this is line 1.
this is line 2.
this is line 3.
this is line 4.

$ awk '{print "hello world!"}' data.txt 
hello world!
hello world!
hello world!
hello world!

文本数据字段变量

awk会自动给一行中的每个数据元素分配一个变量,默认情况下,awk会将如下变量分配给它在文本行中发现的数据字段

  • $0 代表整个文本行;
  • $1 代表文本行中的第1个数据字段;
  • $2 代表文本行中的第2个数据字段;
  • $n 代表文本行中的第n个数据字段。

在文本行中,每个数据字段都是通过字段分隔符划分的。awk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。 awk中默认的字段分隔符是任意的空白字符(例如空格或制表符)。

$ cat data.txt
one line of test text.
two line of test text.
three line of test text.

$ awk '{print $1}' data.txt
one
two
three

如果你要读取采用了其他字段分隔符的文件,可以用 -F 选项指定字段分割符

下面例子指定冒号 (:)作为分割符来读取 /etc/passwd 文件:

$ awk -F: '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
operator
games
ftp
nobody
[...]

在程序脚本中使用多个命令

$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}'
My name is Christine

从文件中读取程序

把awk程序保存到 commands.awk 文件中,然后 awk 命令从文件中读取程序

$ cat commands.awk
{print $1 "'s home directory is " $6}

$ awk -F: -f commands.awk /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
sync's home directory is /sbin
shutdown's home directory is /sbin
halt's home directory is /sbin
mail's home directory is /var/spool/mail
[...]
heyb's home directory is /home/heyb

处理数据前要执行的语句

BEGIN 关键字用来指定awk程序在处理数据前要执行的语句

$ cat data.txt
line 1.
line 2.
line 3.

$ awk 'BEGIN {print "data.txt file contents:"} {print $0}' data.txt
data.txt file contents:
line 1.
line 2.
line 3.

处理数据后要执行的语句

END 关键字用来指定awk程序在处理数据后要执行的语句

$ cat data.txt
line 1.
line 2.
line 3.

$ awk 'BEGIN {print "data.txt file contents:"} {print $0} END {print "end of file"}' data.txt
data.txt file contents:
line 1.
line 2.
line 3.
end of file
posted @ 2022-02-17 16:55  heyb7  阅读(482)  评论(0编辑  收藏  举报