Linux sed 命令详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux sed 命令详解相关的知识,希望对你有一定的参考价值。

sed命令行格式:sed [options] 'command' file(s)
options常用选项:
-n或--quiet或——silent:仅显示script处理后的结果;
-e:以选项中的指定的script来处理输入的文本文件;
-f:以选项中指定的script文件来处理输入的文本文件;
-r∶sed 的动作支援的是延伸型正规表示法的语法;
-i∶直接修改读取的档案内容,而不是由萤幕输出;
-h或--help:显示帮助;
-V或--version:显示版本信息。
Command常用命令:
a:新增,a 的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行);
c:取代,c 的后面可以接字符串,这些字符串可以取代 n1,n2 之间的行;
d:删除,d 后面通常不接任何字符串;
i:插入,i 的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行);
p:列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起运作;
s:取代,可以直接进行取代的工作,通常与正规表达式搭配使用。
实例说明:
新增操作:a命令
sed '/^bird/a\test' file将test追加到 以bird开头的行后面
删除操作:d命令
sed '/^$/d' file #删除空白行;
sed '2d' file #删除第二行;
sed '2,$d' file #删除第2行到最后一行;
sed '$d' file #删除最后一行;
sed '/^bird/'d file #删除所有开头是bird的行;
插入操作:i命令
sed -i '3i\bird ' bird.conf #在bird.conf文件第3行之前插入bird
替换文本中的字符串:s命令
sed 's/bird/birds/' file #将文本中的bird替换成birds;
sed -i 's/ bird / birds /g' file #将file文件中每一行的第一个bird替换为birds;
参考技术A

一、基本正则列表与扩展正则
二、sed 文本处理工具的用法介绍
三、认识sed工具的 n、r、i 选项指令
四、认识sed工具的 p、d、s 条件指令
五、认识sed工具的 i、a、c 条件指令
六、认识sed高级应用 r、w 条件指令
七、综合案例

Linux sed
Stream EDitor,流式编辑器

一、基本正则列表与扩展正则
根据推出时间的不同,分为基本正则和扩展正则,扩展正则在基本正则的基础上做了一些优化和新增了一些正则表达符号,因为一些命令没有及时更新,所以你可能会遇到有些命令只支持基本正则,不支持扩展正则,或者需要添加一些支持扩展正则的选项。
关于正则与扩展正则区别的详细介绍可以看我另一篇shell 正则的介绍

基本正则列表

扩展正则列表

二、sed 文本处理工具的用法介绍:

相关说明如下:

sed命令的常用选项如下:

sed命令常用条件指令如下

三、认识sed工具的 n、r、i 选项指令
# sed [选项] \'条件指令\' 文件.. ..
上面的条件指令 我们可以拆分为 # sed [选项] \'范围界定+操作指令\' 文件.. .. 的格式这样更容易理解

sed命令可以使用行号或正则做为条件匹配:

1)sed命令的 -n 选项
执行p打印等过滤操作时,希望看到的是符合条件的文本。但不使用任何选项时,默认会将原始文本一并输出,从而干扰过滤效果。比如,尝试用sed输出/etc/hosts的第1行:

可以发现所有的行都被显示出来了(第1行重复2次)。—— 正确的用法应该添加 -n 选项,这样就可以只显示第1行了:

行号可以是连续的行号,如打印passwd第3到第6行账户的信息:

2)sed命令的 -r 选项
需要用到扩展正则时,需要加 -r选项

3)sed命令的 -i 选项
正常情况下,sed命令所做的处理只是把操作结果(包括打印、删除等)输出到当前终端屏幕,而并不会对原始文件做任何更改:

若希望直接修改文件内容,应添加选项 -i 。
需要特别注意的是,和一些指令组合和使用的先后顺序都会对最终输出结果产生影响
例如: 指令 -n、和操作操作p

比如,直接删除test.txt(自行创建一个任意内容的文件)的第1~4行:

下文中关于使用sed修改文件的示例中,为了避免大家在练习过程中因误操作导致系统故障,命令省略 –i 选项,不再逐一说明。需要时,大家可自行加上此选项。
4)多个指令可以使用分号隔离
用分号来隔离多个操作,比如:

1)行号案例

2)正则案例

3)没有条件,则表示匹配所有行

四、认识sed工具的 p、d、s 条件指令
1)下面看看sed工具的p指令案例集锦(自己提前生成一个a.txt文件)

2)下面看看sed工具的d指令案例集锦(自己提前生成一个a.txt文件)

3)sed命令的s替换基本功能(s/旧内容/新内容/选项):

4)下面看看sed工具的s指令案例集锦(自己提前生成一个a.txt文件)
注意:替换操作的分隔“/”可改用其他字符,如#、&等,便于修改文件路径

以下操作使用nssw.txt作为测试文件。
参考数据文件内容如下:

5)删除文件中每行的第二个、最后一个字符
分两次替换操作,第一次替换掉第2个字符,第二次替换掉最后一个字符:

6)将文件中每行的第一个、倒数第1个字符互换
每行文本拆分为“第1个字符”、“中间的所有字符”、“倒数第1个字符”三个部分,然后通过替换操作重排顺序为“3-2-1”:

7)删除文件中所有的数字
因原文件内没有数字,行首也没有空格,这里在内容中新增一些数字另外新几行内容 首行添加几行空格,生成一个新测试文件以 nssw2.txt

以nssw2.txt文件为例,删除所有数字、行首空格的操作如下:

8)为文件中每个大写字母添加括号[]
使用“()”可实现保留功能,所以可参考下列操作解决:

五、认识sed工具的 i、a、c 条件指令
# sed [选项] \'条件指令\' 文件..
sed工具的多行文本处理操作:

基本语法格式案例:

1)sed命令的 i 指定行之前插入基本功能

2)sed命令的a追加基本功能

3)sed命令的c替换基本功能

六、认识sed高级应用 r、w 条件指令

1)sed命令的 r 读取文件

2)sed命令的 w 保存到文件

七、综合案例
1) 综合案例1

2)综合案例2 脚本应用

linux文本三剑客之sed命令详解

linux文本三剑客之sed命令详解



1.sed命令详解

sed称为流编辑器,处理流程如下:

  1. 一次处理一行内容,把当前处理的行缓存在临时缓存区中,称为“模式空间”,用sed命令处理缓存区的内容,处理完成后,把缓冲区的内容送往屏幕。sed默认不会修改源文件数据。
  2. 当一行数据匹配完成后,它会继续读取下一行数据,并重复这个过程,直到将文件中所有数据处理完毕。

sed命令的语法结构如下:

sed [OPTION]... {script} [input-file]...

{script}:为地址命令

sed命令的选项如下:

选项 说明
-n 默认情况下,sed 会在所有的脚本执行完毕后,会自动输出处理后的内容,而该选项会屏蔽自动输出,需使用 p 命令来完成输出。
-e 多点编辑,逻辑与,需同时匹配前后两者内容才会输出
-f 从指定文件中读取编辑文件
-r 支持使用扩展正则表达式,默认支持标准正则表达式
-i 此选项会直接修改源文件

1.1 地址定界和编辑命令

  • 地址定界

    地址定界符 说明
    不给地址 默认进行全文处理
    # 指定的#行
    $ 最后一行
    /pattern/ 被此模式匹配到的每一行
    m,n 从第m行到第n行
    m,+n 从第m行到m+n行
    /pat1/,/pat2/ 从模式1的行到模式2的行
    m,/pat1/ 从第m行到被模式1匹配的行
    ~ 步进
  • 编辑命令

    命令 选项
    d 删除模式空间的行,并开启下一行
    p 打印当前模式匹配的行,追加到指定行之后
    a [\\]text 在指定的行后面追加文本,\\用于读特殊符号转义,\\n换行实现多行文本追加
    i [\\]text 在指定的行之前追加文本
    c [\\]text 把指定的行替换为text文本
    w FILE 保存模式匹配的行到FILE文件
    r FILE 读取指定文件的行到模式空间中,匹配到指定行后
    ! 模式空间的行取反处理
    = 为模式空间的行打印行号

    sed用法示例如下:

    #示例一:sed默认会对没有处理的行自动打印,对要处理的行再次处理一次
    [root@xuzhichao ~]# seq 1 5 | sed \'2p\'
    1
    2
    2
    3
    4
    5
    
    #示例二:-n选项,会把没有处理的内容不输出到屏幕
    [root@xuzhichao ~]# seq 1 5 | sed -n \'2p\'
    2
    
    #示例三:sed的地址定界功能
    [root@xuzhichao ~]# sed -n \'/^root/,/^bin/p\' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    rooter:x:1002:1002::/home/rooter:/bin/bash
    [root@xuzhichao ~]# sed -n \'/^root\\>/,/^bin/p\' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    
    #示例四:sed的地址定界功能
    [root@xuzhichao ~]# sed -n \'2p\' /etc/passwd
    bin:x:1:1:bin:/bin:/sbin/nologin
    [root@xuzhichao ~]# sed -n \'2,3p\' /etc/passwd
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    [root@xuzhichao ~]# sed -n \'2,+1p\' /etc/passwd
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    [root@xuzhichao ~]# sed -n \'/^root\\>/,3p\' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    
    #示例五:打印奇数行和偶数行
    [root@xuzhichao ~]# seq 6 | sed -n \'1~2p\'
    1
    3
    5
    [root@xuzhichao ~]# seq 6 | sed -n \'2~2p\'
    2
    4
    6
    
    #示例六:-e选项,同时实现多个选项
    [root@xuzhichao ~]# seq 5 | sed -n -e \'2p\' -e \'4p\'
    2
    4
    
    #示例七:d选项,删除,不加地址定界会删除全部内容,与-n选项合用也会删除全部内容
    [root@xuzhichao ~]# seq 5 | sed \'d\'
    [root@xuzhichao ~]# seq 5 | sed -n \'4d\'
    [root@xuzhichao ~]# seq 5 | sed \'1,4d\'
    2
    3
    5
    
    #示例八:!的取反功能
    [root@xuzhichao ~]# seq 5 | sed -n \'4!p\'
    1
    2
    3
    5
    [root@xuzhichao ~]# seq 5 | sed \'4!d\'
    4
    
    #示例九:显示行号
    [root@xuzhichao ~]# sed -n \'/root/=\' /etc/passwd
    1
    10
    39
    40
    [root@xuzhichao ~]# sed -n -e \'/root/=\' -e \'/root/p\' /etc/passwd
    1
    root:x:0:0:root:/root:/bin/bash
    10
    operator:x:11:0:operator:/root:/sbin/nologin
    39
    admroot:x:1001:1001::/home/admroot:/bin/bash
    40
    rooter:x:1002:1002::/home/rooter:/bin/bash
    
    #示例十:a选项追加内容
    [root@xuzhichao ~]# seq 5 | sed \'2,4a\\ =======\\n\\ =======\'
    1
    2
     =======    <==空格后跟多个等号=
     =======
    3
     =======
     =======
    4
     =======
     =======
    5
    
    #示例十一:a追加的用法:在~/.bashrc文件中增加两个别名项。
    [root@xuzhichao ~]# 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@xuzhichao ~]# sed \'/specific/aalias cdnet="cd /etc/sysconfig/network-scripts/"\\nalias p=poweroff\' ~/.bashrc 
    # .bashrc
    # User specific aliases and functions
    alias cdnet="cd /etc/sysconfig/network-scripts/"
    alias p=poweroff
    
    alias rm=\'rm -i\'
    alias cp=\'cp -i\'
    alias mv=\'mv -i\'
    
    # Source global definitions
    if [ -f /etc/bashrc ]; then
    	. /etc/bashrc
    fi
    
    #示例十二:i选项用于在指定行前面增加内容
    [root@xuzhichao ~]# seq 5 | sed  \'2iabc\\nefg\'
    1
    abc
    efg
    2
    3
    4
    5
    
    #示例十三:c选项用于替换指定行的内容
    [root@xuzhichao ~]# seq 5 | sed  \'1,2cabc\\nefg\'
    abc
    efg
    3
    4
    5
    
    #示例十四:w选项可以保存指定行到一个文件中
    [root@xuzhichao ~]# seq 5 | sed -n \'2,4wf1\'
    [root@xuzhichao ~]# cat f1
    2
    3
    4
    
    #示例十五:r选项可以读取指定的文件内容追加到指定的行后
    [root@xuzhichao ~]# cat f2
    1
    2
    3
    [root@xuzhichao ~]# seq 4 6 | sed \'3r f2\'
    4
    5
    6
    1
    2
    3
    
    #示例十六:-i选项可以直接修改原始文件,-i.bak可以先备份原始文件,然后再修改原始文件
    [root@xuzhichao ~]# cat f2
    1
    2
    3
    [root@xuzhichao ~]# sed -i \'2aabc\' f2
    [root@xuzhichao ~]# cat f2
    1
    2
    abc
    3
    [root@xuzhichao ~]# sed -i.bak \'2aabc\' f2
    [root@xuzhichao ~]# cat f2
    1
    2
    abc
    abc
    3
    [root@xuzhichao ~]# cat f2.bak 
    1
    2
    abc
    3
    

1.2 搜索替换

sed可以实现搜素替换功能,比较常用,命令格式为:

[address]s/pattern/replacement/flags

[address]:代表地址定界;
pattern:指的是需要替换的内容,支持正则表达式;
replacement:指的是要替换的新内容,支持后向引用;
其中/可以换为@或#,即s@@@或s###;

常用的flags意义如下:

flags 说明,
g 对一行中的数据匹配到的内容全部进行替换,如果没有 g,则只会对匹配到的第一个数据做替换操作。
p 打印替换命令中被pattern模式匹配到的行,此标记通常与 -n 选项一起使用
n 1~512 之间的数字,表示要替换第几次被模式匹配到的字符串,例如,一行中有 3 个 A,但用户只想替换第二个 A,则n为2
w file 将缓冲区中的内容写到指定的 file 文件中
& 后向引用,表示对[address]s/pattern/replacement/flags中的pattern匹配的内容进行引用
\\n 后向引用,表示对pattern中的内容匹配的第 n 个小括号分组匹配中的内容进行引用。
\\ 用于转义特殊字符

sed搜索替换相关的使用示例如下:

#示例一:sed的搜索替换如果不加flag,默认只对一行中匹配的地址pattern进行替换;
#使用数字n可以指定对第n个匹配到的pattern进行替换;
#使用g会对所有匹配到的pattern进行替换;
[root@xuzhichao ~]# echo "root root root" | sed \'s/root/admin/\'
admin root root
[root@xuzhichao ~]# echo "root root root" | sed \'s/root/admin/2\'
root admin root
[root@xuzhichao ~]# echo "root root root" | sed \'s/root/admin/g\'
admin admin admin

#示例二:w选项,会把匹配到的行写入文件中;
#注意:仅仅会把pattern匹配到的行写入文件中,而且是被替换之后;
[root@xuzhichao ~]# seq 5 | sed -n \'s/1/11/w f1\'
[root@xuzhichao ~]# cat f1
11

#示例三:&的应用:不需要使用分组(),会对pattern匹配到的所有内容进行引用
[root@xuzhichao ~]# echo "root root root" | sed \'s/root/&er/g\'
rooter rooter rooter

#示例四:把/etc/default/grub的第六行之后增加single单用户模式
[root@xuzhichao ~]# cat /etc/default/grub 
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed \'s, release .*$,,g\' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet net.ifnames=0"
GRUB_DISABLE_RECOVERY="true"
[root@xuzhichao ~]# sed \'/GRUB_CMDLINE_LINUX/s/"$/ single&/\' /etc/default/grub 
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed \'s, release .*$,,g\' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet net.ifnames=0 single"
GRUB_DISABLE_RECOVERY="true"

#示例五:取路径的基名和文件夹名
#basename命令用于取文件路径中的文件名
#dirname命令用于取文件路径中的目录名
[root@xuzhichao ~]# echo "/etc/httpd/conf/httpd.conf" | sed -r \'s@(^.*/)([^/]+/?$)@\\1@\'
/etc/httpd/conf/
[root@xuzhichao ~]# echo "/etc/httpd/conf/httpd.conf" | sed -r \'s@(^.*/)([^/]+/?$)@\\2@\'
httpd.conf
[root@xuzhichao ~]# basename /etc/httpd/conf/httpd.conf
httpd.conf
[root@xuzhichao ~]# dirname /etc/httpd/conf/httpd.conf
/etc/httpd/conf

#示例六:删除/etc/grub2.cfg文件中所有以空白开头的行行首的空白字符
[root@xuzhichao ~]# sed -r \'s/^[[:space:]]+(.*)/\\1/\' /etc/grub2.cfg

#示例七:注释/etc/fstab中的所有行
[root@xuzhichao ~]# sed -r \'s/.*/#&/\' /etc/fstab
[root@xuzhichao ~]# sed -r \'s/^[^#]+/#&/\' /etc/fstab 

#示例八:把/etc/httpd/conf/httpd.conf文件中所有注释行删除,grep -v "^$"用于删除空行
[root@xuzhichao ~]# sed -r \'s/^[[:space:]]*#.*//\' /etc/httpd/conf/httpd.conf | grep -v "^$"

#示例九:使用sed取出ifconfig中的本机ip地址
[root@xuzhichao ~]# ifconfig eth0 | sed -r \'2!d;s/.*inet (.*) netmask.*/\\1/\'
192.168.20.17 

#示例十:对centos中光盘所有rpm包基于cpu架构(倒数第二个字段)统计并排序
[root@xuzhichao ~]# ls /misc/cd/Packages/*.rpm | tr -s \' \' \'\\n\' | sed -r \'s/^.+\\.([^.]+)\\.rpm$/\\1/\' | sort | uniq -c | sort -rn
[root@xuzhichao ~]# ls /misc/cd/Packages/*.rpm | tr -s \' \' \'\\n\' | awk -F. \'{print $(NF-1)}\' | sort | uniq -c | sort -rn
[root@xuzhichao ~]# ls /misc/cd/Packages/*.rpm | tr -s \' \' \'\\n\' | rev | cut -d. -f 2 | rev | sort | uniq -c | sort -rn
   4649 x86_64
   3154 noarch
   2267 i686

#示例十一:统计/etc/init.d/functions中单词总数
#处理思路:单词只包括字符串中包含字母,数字,其他字符都是单词的分隔符
[root@xuzhichao ~]# cat /etc/init.d/functions | tr -s " " | sed -r \'s/[^[:alnum:]]/\\n/g\' | wc -l

1.3 sed高级用法

sed命令在处理数据时的缓存区有模式空间和保持空间两个空间,利用这两个空间可以实现一些高级编辑功能。

sed命令的以下子命令意义如下:

命令 意义
P 同 d 和 D 之间的区别一样,P(大写)命令和单行打印命令 p(小写)不同,对于具有多行数据的模式空间来说,它只会打印模式空间中的第一行,也就是首个换行符之前的所有内容;p会打印模式空间的所有内容
h 将模式空间中的内容复制到保持空间
H 将模式空间中的内容附加到保持空间
g 将保持空间中的内容复制到模式空间
G 将保持空间中的内容附加到模式空间
x 交换模式空间和保持空间中的内容
n 读取匹配到的行的下一行覆盖至模式空间
N 将匹配到的行的下一行文本内容添加到模式空间已有数据之后(之间用换行符分隔),从而使前后两个文本行同时位于模式空间中,sed 命令会将这两行数据当成一行来处理。
d 删除模式空间的行,然后读取下一行继续删除
D 其作用是只删除缓冲区中的第一行,即将缓冲区中第一个换行符(包括换行符)之前的内容删除掉。

高级用法使用示例:

#示例一:使用sed匹配2,然后把2和2的下一行3读入模式空间中,把换行符替换为空格
[root@xuzhichao ~]# seq 5 | sed \'/2/{N;s/\\n/ /}\'
1
2 3
4
5
[root@xuzhichao ~]# seq 5 | sed \'{N;s/\\n/ /}\'
1 2
3 4
5

#示例二:
#查找字符串2,然后把2和2的下一行3读入模式空间,在模式空间中匹配2,匹配到后把第一个换行前的内容删除
[root@xuzhichao ~]# seq 5 | sed \'/2/{N;/2/D}\'
1
3
4
5

#示例三:查找字符串2,然后把2和2的下一行3读入模式空间,在模式空间中匹配2,匹配到后把模式空间所有内容删除
[root@xuzhichao ~]# seq 5 | sed \'/2/{N;/2/d}\'
1
4
5
[root@xuzhichao ~]# seq 5 | sed \'/2/{N;d}\'
1
4
5

#示例四:一次读取两行到模式空间,第一读取前两行,匹配到2,删除1;第二次读取第2行和第3行,匹配到2,把2删除。
[root@xuzhichao ~]# seq 5 | sed \'N;/2/D\'
3
4
5
[root@xuzhichao ~]# seq 5 | sed \'N;/3/D\'
1
2
4
5

#示例五:sed命令一次读取两行,第一次读取前两行,输出换行符之前的内容,即1,然后sed自动把模式空间内容输出,接着读取第3行和第4行,依次类推。
[root@xuzhichao ~]# seq 5 | sed \'N;P\'
1
1
2
3
3
4
5
[root@xuzhichao ~]# seq 5 | sed \'N;p\'
1
2
1
2
3
4
3
4
5

#示例六:打印奇数行和偶数行
[root@xuzhichao ~]# seq 5 | sed  \'n;d\'
1
3
5
[root@xuzhichao ~]# seq 5 | sed -n \'n;p\'
2
4

#示例七:倒序显示tac,实现步骤如下
#1、读取第1行内容1,1!G不匹配第一行,h把1放入保持空间,$!d把不是最后一行的模式空间内容清空,此时保持空间为1;
#2、读取第2行内容2,1!G把1追加到模式空间,h把2\\n1放入保持空间,$!d把不是最后一行的模式空间内容清空,此时保持空间为2\\n1;
#3、依次类推,当最后一行读入时,模式空间不会清空,直接打印出来即为倒序显示;
[root@xuzhichao ~]# seq 5 | sed \'1!G;h;$!d\'
5
4
3
2
1
[root@xuzhichao ~]# seq 5 | sed -n \'1!G;h;$p\'
5
4
3
2
1

#示例八:取最后一行
[root@xuzhichao ~]# seq 5 | sed \'N;D\'
5
[root@xuzhichao ~]# seq 5 | sed \'$!d\'
5

#示例九:取最后两行
[root@xuzhichao ~]# seq 5 | sed \'$!N;$!D\'
4
5

#示例十:每行后追加空行
[root@xuzhichao ~]# seq 5 | sed \'G\'
1

2

3

4

5

以上是关于Linux sed 命令详解的主要内容,如果未能解决你的问题,请参考以下文章

Linux学习011sed命令详解

sed命令详解

linux sed命令参数及用法详解

linux之sed命令详解

linux sed命令详解(转)

linux sed 命令详解