简体   繁体   中英

Shell, find line containing, and replace on only line after it

I have a file located at: /Applications/Firefox.app/Contents/info.plist . I am trying to find the line that contains LSUIElement and then replace that whole line with <string>firefox</string> .

This is what I got, so far, it does nothing:

line=“LSUIElement”
rep="<string>firefox</string>"
sed -e "s/${line}/${rep}/g" /Applications/Firefox.app/Contents/info.plist > /Applications/Firefox.app/Contents/info.plist.profilist
mv /Applications/Firefox.app/Contents/info.plist.profilist /Applications/Firefox.app/Contents/info.plist 

With awk , you can do something like this:

awk '/LSUIElement/{i=NR+1}{if(NR==i){gsub(/1/,"0",$0)}}1' File > tmp && mv tmp File

Logic: if LSUIElement is found, set variable i to NR+1 (ie the next line /record number). As second part, if NR (current record/line) is i (previously saved), substitute 1 with 0 . Thus substitution will happen only in the line following the line with pattern LSUIElement .

Let's simplify the problem for those, who don't have such a plist-file at hand.

echo -e "foo bar baz\nthe suspicious LSUIElement in line 2\nand some more garbage" > luis.txt 
cat luis.txt
foo bar baz
the suspicious LSUIElement in line 2
and some more garbage

line=“LSUIElement”
rep="<string>firefox</string>"

So here we have problem 1: Slashes in sed expression normally separate the parts like in 's/a/b/g'.

sed -e "s/${line}/${rep}/g" luis.txt > luis2.txt 
mv luis2.txt luis.txt 

We first go for the low hanging fruit. Sed, at least Gnu-sed, has an option -i to alter the file in place. In the background, there might be a copy being made, since often the file will shrink or grow, so it is hard to avoid, but we don't need the redirection and mv:

sed -i "s/${line}/${rep}/g" luis.txt

You might even let sed produce a backup file. AFAIK sed doesn't need -e in normal cases, but it might be, that gnu-sed is special in that case. Test or read the man page.

Now for the hard part. We test without -i to avoid destruction. First test, first surprise:

line=“LSUIElement”
rep="<string>firefox</string>"
echo $line
# “LSUIElement”
echo $rep
# <string>firefox</string>

Why does line show quotes, but rep doesn't? Ah, these are typographical quotes? Is this of any importance? Then you should mention it, because it's easy to oversee. I decide to go without them until demand.

If we replace the / in the sed-expression with |, we can avoid collision with sed-syntax in the $rep variable:

sed "s|${line}|${rep}|g" luis.txt
foo bar baz
the suspicious <string>firefox</string> in line 2
and some more garbage

Here we replaced exactly the pattern, not the line.

sed "s|.*${line}.*|${rep}|g" luis.txt
foo bar baz
<string>firefox</string>
and some more garbage

This was replacing the whole line.

sed "s|${line}.*|${rep}|g" luis.txt
foo bar baz
the suspicious <string>firefox</string>
and some more garbage

This was replacing from pattern inclusive till the EOL.

If we want to replace something in the next line - well replace what? For example, the garbage ?

We can match on $line without replacing it, but calling a short series of 2 commands, 'n' for 'read next line' and perform our s-substitution there. Those commands have to be wrapped into {}. Since we don't need changing / to | in the former part, we switch back there:

sed "/${line}.*/{n;s|garbage|$rep|}" luis.txt
foo bar baz
the suspicious LSUIElement in line 2
and some more <string>firefox</string>

The command:

sed -i.bak "/${line}.*/{n;s|garbage|$rep|}" luis.txt

would create such a backupfile luis.txt.bak

With a pattern, as now noticed in the comment, this would look like:

sed -i.bak "/${line}.*/{n;s|<string>.*</string>|$rep|}" luis.txt

Decide, how specific (1, digits, doesn't matter) it has to be.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM