[shell] sed 插入多行文本

本文介绍如何使用sed命令在Linux和Mac环境下,删除文件中2-7行的内容,并在第2行插入多行文本,提供了一个包含条件判断的脚本实例。

sed 命令插入多行文本,感觉这个操作有点费劲,所以在这记录一下。

🔥 文章来源:《[shell] sed 插入多行文本


1. 脚本意图

  1. 删除 2 - 7 行的文本。
  2. 从第 2 行插入多行文本。

2. 脚本使用

./script your_path

3. 脚本源码

#!/bin/sh

work_path=$(dirname $0) 
cd $work_path
work_path=$(pwd)

if [ $# -ne 1 ]; then
    echo "./script [file_name]"
    exit 1
fi

file_name=$1

insert='i\
if test \"x${ac_cv_env_CFLAGS_set}\" = \"x\"; then : \ 
    CFLAGS=\"-fPIC\" \
fi \
if test \"x${ac_cv_env_CXXFLAGS_set}\" = \"x\"; then : \ 
    CXXFLAGS=\"-fPIC\" \
fi
'

insert_func() {
    if [ $(uname -s) == "Darwin" ]; then
        # mac
        sed -i "" "2,7d" $file_name
        sed -i "" "2$insert" $file_name
    else
        # linux
        sed -i "2,7d" $file_name
        sed -i "2$insert" $file_name
    fi  
}

insert_func

4. 参考

<think>我们面对一个文本处理需求:使用sed在指定行插入文本并保持对齐根据历史对话:用户之前要求在18行插入一段XML注释:<!--paramname="mylogname"value="true"/>现在的新需求是:插入文本要与上一行(即原第17行)对齐分析:对齐通常指缩进(行首空白字符)与上一行保持一致因此需要先获取原第17行的行首空白(前导空格或制表符),然后将同样的空白添加到插入行的开头实现思路:1.提取原第17行的前导空白2.将提取的空白字符串添加到要插入文本之前3.使用sed在第18行前插入处理后的文本(包含前导空白)挑战:sed本身无法捕获匹配行的前导空白,我们需要借助其他工具(如awk)来获取前导空白分步解决方案:步骤1:获取第17行的前导空白使用awk处理:awk'NR==17{match($0,/^[\t]*/);printsubstr($0,1,RLENGTH);exit}'文件名步骤2:将要插入文本与捕获的空白前缀拼接例如:空白前缀+"<!--paramname=\"mylogname\"value=\"true\"/>"步骤3:使用sed插入拼接后的字符串注意:因为要插入文本可能包含特殊字符(如/,&等),所以需要先转义但是,更简单的方式是使用临时变量存储前导空白,然后构建sed命令具体步骤(以bash脚本为例):#!/bin/bashfile="yourfile.txt"#获取第17行的前导空白whitespace=$(awk'NR==17{match($0,/^[\t]*/);printsubstr($0,RSTART,RLENGTH);exit}'"$file")#构建要插入文本(保持缩进)insert_text="${whitespace}<!--paramname=\"mylogname\"value=\"true\"/>"#转义insert_text中的特殊字符(如/)以便在sed中使用#注意:因为我们要在sed的i命令中使用,所以需要转义反斜杠和换行,但i命令允许使用转义换行,所以我们可以用多行形式,但这里只插入一行,所以用反斜杠转义特殊字符#简单转义:将文本中的/替换为\/(如果文本中有其他特殊字符也要转义,如&、\等)#但更安全的是使用sed的i命令,它允许插入多行,但插入固定文本时,我们只需转义反斜杠和单引号(因为我们用单引号包裹),但这里我们使用双引号变量会有变量展开问题。所以我们可以用另一种方法:将插入文本存入一个临时文件,然后使用sed的r命令,但这样更复杂。#替代方案:不转义,而是使用不同的sed命令分隔符(但我们这里使用i命令,且文本中可能包含/,所以我们可以用|作为分隔符)#例如:sed-i"18i\\$insert_text""$file"#但是使用双引号会展开变量,且要求insert_text中不包含特殊字符(如!等)可能出错,更安全的方法:#由于insert_text是变量,且可能包含特殊字符,我们可以使用以下技巧:#使用sed的i命令,并且用反斜杠将变量中的特殊字符转义,但只转义反斜杠和换行(因为i命令会终止于换行)#因为我们插入的是一行,所以只需要将insert_text中的反斜杠进行转义(将每个反斜杠替换成两个反斜杠)#具体操作:将insert_text中的'\'替换为'\\'(这样在sed命令中表示一个反斜杠)#另外,由于我们使用双引号来引用整个sed命令,还需要转义$(如果有变量)和双引号等,但这里没有。另外,如果insert_text包含换行,会导致命令错误,所以确保是一行。#但是我们的insert_text就是一行,所以:#escaped_insert_text=$(echo"$insert_text"|sed's/\\/\\\\/g')#然后使用:sed-i"18i$escaped_insert_text""$file"#或者,使用更简单的方法:用单引号包裹,但是单引号内不能包含单引号,所以先替换单引号。#替换:将insert_text中的单引号替换为'\''(即:先结束单引号,再用转义的单引号,然后重新开始单引号)#如:insert_text="abc'def"->转换为:'abc'\''def'#escaped_insert_text=$(echo"$insert_text"|sed"s/'/'\\\\''/g")然后sed-i'18i'"$escaped_insert_text"这样不安全#推荐使用perl或更高级的工具,但这里我们按照原问题使用sed。#简化方法:使用临时文件(避免引号问题)#我们可以先将要插入文本写入临时文件,然后使用sed的r命令。但是sed的r命令是读取文件插入到匹配行后,而我们要求是插入到第18行前,所以我们可以在第18行前插入一个空白行,然后再用临时文件的内容替换该行?这样复杂。#综合考虑,使用awk来处理整个插入过程会更简单。我们改用awk一步到位:awk-vn=18-vinsert="<!--paramname=\"mylogname\"value=\"true\"/>"'NR==n{printprev_whitespaceinsert}{print}{prev_whitespace=""}NR==n-1{match($0,/^[\t]*/);prev_whitespace=substr($0,1,RLENGTH)}'文件名>文件名.new&&mv文件名.new文件名解释:-用变量n表示插入位置(第n行)-insert变量为要插入文本-过程:我们记录第n-1行的前导空白(通过match函数匹配前导空白)当处理到第n行时,先打印出“用第n-1行的前导空白+insert”构成的字符串,然后再打印第n行的原内容其他行直接打印但是,注意:第n-1行必须在第n行之前,所以我们可以:1.记录第n-1行的前导空白,然后在处理第n行时使用2.处理每一行时,记录当前行的前导空白(用于下一行?不,我们只需要第n-1行的)具体步骤:初始化一个变量prev_whitespace为空在NR==n-1时,获取这一行的前导空白,存储在prev_whitespace中在NR==n时,打印prev_whitespace和insert构成的字符串(即插入的行)然后打印当前行(即原第n行,现在变成了第n+1行)注意:当n=1时,没有前一行,所以需要特殊处理,但这里n=18,不用考虑。因此,完整的awk命令为:awk-vn=18-vinsert_text="<!--paramname=\"mylogname\"value=\"true\"/>"'NR==n-1{#匹配前导空白match($0,/^[\t]*/)whitespace=substr($0,1,RLENGTH)}NR==n{#插入新行printwhitespaceinsert_text}{print}#打印每一行(包括原第n行)'输入文件>输出文件注意:由于在打印第n行时,我们同时打印了插入行(在它前面),所以顺序正确。但是这样写,第n-1行会被正常打印(因为print在最后),而第n行也会被打印两次?不,不会。注意:第n-1行:匹配到NR==n-1,然后执行其中的块,然后执行{print}(打印第n-1行)第n行:执行NR==n块:打印插入行(即新行),然后执行{print}打印原第n行其他行:只执行{print}所以原第n行被打印了两次?不对,我们只需要在打印第n行之前先打印插入行,然后打印原第n行。所以,这其实是:在n行前插入一行,所以该命令正确。但是,如果文件很大,我们希望处理到第n行后就退出,可以优化性能。但这里我们不处理。更简洁的方式:awk-vn=18-vtext="<!--paramname=\"mylogname\"value=\"true\"/>"'NR==n-1{whitespace=$0;sub(/[^\t].*$/,"",whitespace)}#这里用sub删掉非空白开头后面的所有,保留前导空白NR==n{printwhitespacetext}1'文件名>文件名.newmv文件名.new文件名解释:sub函数将第一个非空白字符开始到行尾的部分替换为空,从而保留前导空白。但是注意:如果第n-1行是全空白行?那么whitespace就是整行,但通常我们的行会有内容。测试用例:第17行:<example>(前面有4个空格)第18行:</example>插入后:第18行变为:<!--param...-->因此,命令如下:file="yourfile"awk-vn=18-vtext="<!--paramname=\"mylogname\"value=\"true\"/>"'NR==n-1{#提取前导空白if(match($0,/^[\t]*/)){whitespace=substr($0,RSTART,RLENGTH)}else{whitespace=""}}NR==n{printwhitespacetext#插入行}{print}'"$file">"$file.tmp"&&mv"$file.tmp""$file"但是,用户要求用sed,而这里使用了awk,所以我们需要考虑用户要求。如果坚持使用sed,可以结合shell变量和sed:先获取第17行的前导空白,然后构建插入行,然后使用sed插入(但要处理特殊字符)。我们选择使用awk因为更简单可靠。因此,回答用户:要实现插入文本并与上一行对齐,关键在于捕获上一行的缩进空白。由于sed无法轻松获取前导空白,我们使用awk处理。注意:如果用户一定要用sed,我们可以用:whitespace=$(grep-n""文件名|grep"^17:"|sed's/^[0-9]*:\([\t]*\).*/\1/')但是grep可能不够精确(行号用:分隔,可能和内容冲突),所以我们还是用awk取第17行。所以,我们给出两种方法:方法一(推荐):使用awk方法二:使用sed结合shell我们重点写awk方法。最终回答:</think> ### 如何使用 sed 插入文本并与上一行对齐? 在文本处理中,若要在指定行插入内容并保持与上一行相同的缩进格式(如空格或制表符),需结合 `sed` 的插入功能和缩进捕获机制。以下是详细步骤和示例: --- #### **步骤解析(以在第 18 行插入为例)** 1. **捕获上一行的缩进** 通过 `sed` 提取第 17 行的前导空白(缩进字符): ```bash indent=$(sed -n '17 s/[^[:space:]].*//p' 文件名) ``` - `17`:目标行号(第 17 行) - `s/[^[:space:]].*//p`:保留行首的空格/制表符,删除非空白内容 2. **构建待插入文本** 将捕获的缩进应用到新内容: ```bash new_content="${indent}<!--param name=\"mylogname\" value=\"true\"/>" ``` 3. **插入并保留缩进** 使用 `sed` 在第 18 行前插入 `$new_content`: ```bash sed -i "18i\\$new_content" 文件名 ``` - `18i\\`:在 18 行前插入 - `-i`:直接修改文件(操作前建议备份) --- #### **完整代码示例** 假设文件 `web.xml` 内容: ```xml <servlet> <servlet-name>default</servlet-name> <!-- 第 17 行 --> <servlet-class>org.example</servlet-class> <!-- 第 18 行 --> </servlet> ``` 执行命令: ```bash # 捕获第 17 行缩进(4 个空格) indent=$(sed -n '17 s/[^[:space:]].*//p' web.xml) # 构建带缩进的新行 new_content="${indent}<!--param name=\"mylogname\" value=\"true\"/>" # 插入到第 18 行前 sed -i "18i\\$new_content" web.xml ``` 结果: ```xml <servlet> <servlet-name>default</servlet-name> <!-- 第 17 行 --> <!--param name="mylogname" value="true"/> <!-- 新插入行 --> <servlet-class>org.example</servlet-class> <!-- 原 18 行下移 --> </servlet> ``` --- #### **关键说明** 1. **对齐原理** 第 17 行的缩进 `[[:space:]]`(如 `4 空格`)被复制到新行,确保视觉对齐。 2. **特殊字符处理** - 若插入内容含 `/` 或 `&`,需用 `\` 转义(如 `\/path`)[^2] - 若缩进含制表符(`\t`),`sed` 会自动保留其格式 3. **备份文件** 安全操作建议: ```bash cp web.xml web.xml.bak # 备份 sed -i "18i\\$new_content" web.xml ``` --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值