Edit part of a line in text without losing other lines - linux

I tried to replace the tstop parameter of the text from 120 to 80. What I got was a single line of text: tstop 80, losing the rest of the text. I used
sed -i -rne 's/(tstop)\s+\w+/\1 80/gip'
I want to change only the line tstop and keep the rest of text as it is.
Part of the text is:
[Grid]
X1-grid 1 -6.0 24 u 6.0
X2-grid 1 -24. 96 u 24.
X3-grid 1 -18.0 72 u 18.0
[Chombo Refinement]
Levels 4
Ref_ratio 2 2 2 2 2
Regrid_interval 2 2 2 2
Refine_thresh 0.3
Tag_buffer_size 3
Block_factor 8
Max_grid_size 64
Fill_ratio 0.75
[Time]
CFL 0.3
CFL_max_var 1.1
tstop 120
first_dt 1.e-5
[Solver]
Solver tvdlf

with GNU sed:
sed -E 's/^(tstop +)[^ ]*/\180/' file
or
sed -E '/^tstop/s/[^ ]+$/80/' file
If you want to edit your file "in place" use sed's option -i.
See: The Stack Overflow Regular Expressions FAQ

The n flag in -rne suppresses the normal output of the sed command. Only lines matching your pattern will be output with your p command. Try this:
sed -i -re 's/(tstop)\s+\w+/\1 80/gi'
A more portable version using BRE(Basic Regular Expresssions) could be:
sed -i -e 's/\(tstop\)\( *\)[[:alnum:]]*/\1\280/' file
Note that spaces after tstop are also captured here, to preserve the file format. Also i and g modifiers seem to be useless in your case.

Related

How do I insert text to the 1st line of a file using sed?

Hi I'm trying to add text to the 1st line of a file using sed
so far iv'e tried
#!/bin/bash
touch test
sed -i -e '1i/etc/example/live/example.com/fullchain.pem;\' test
And this dosn't work
also tried
#!/bin/bash
touch test
sed -i "1i ssl_certificate /etc/example/live/example.com/fullchain.pem;" test
this dosn't seem to work either
oddly when I try
#!/bin/bash
touch test
echo "ssl_certificate /etc/example/live/example.com/fullchain.pem;" > test
I get the 1st line of text to appear when i use cat test
but as soon as i type sed -i "2i ssl_certificate_key /etc/example/live/example.com/privkey.pem;"
I can't see the information that i sould do on line 2 this being ssl_certificate_key /etc/example/live/example.com/privkey.pem;
so my question to summerise
Can text be inserted into the 1st line of a newly created file using sed?
If yes whats the best way of inserting text after the 1st line of text?
Suppose you have a file like this:
one
two
Then to append to the first line:
$ sed '1 s_$_/etc/example/live/example.com/fullchain.pem;_' file
one/etc/example/live/example.com/fullchain.pem;
two
To insert before the first line:
$ sed '1 i /etc/example/live/example.com/fullchain.pem;' file
/etc/example/live/example.com/fullchain.pem;
one
two
Or, to append after the first line:
$ sed '1 a /etc/example/live/example.com/fullchain.pem;' file
one
/etc/example/live/example.com/fullchain.pem;
two
Note the number 1 in those sed expressions - that's called the address in sed terminology. It tells you on which line the command that follows is to operate.
If your file doesn't contain the line you're addressing, the sed command won't get executed. That's why you can't insert/append on line 1, if your file is empty.
Instead of using stream editor, to append (to empty files), just use a shell redirection >>:
echo "content" >> file
Your problem stems from the fact that sed cannot locate the line you're telling it to write at, for example:
touch test
sed -i -e '1i/etc/example/live/example.com/fullchain.pem;\' test
attempts to write to insert at the line 1 of test, but that line doesn't exist at this point. If you've created your file as:
echo -en "\n" > test
sed -i '1i/etc/example/live/example.com/fullchain.pem;\' test
it would not complain, but you'd be having an extra line. Similarly, when you call:
sed -i "2i ssl_certificate_key /etc/example/live/example.com/privkey.pem;"
you're telling sed to insert the following data at the line 2 which doesn't exist at that point so sed doesn't get to edit the file.
So, for the initial line or the last line in the file, you should not use sed because simple > and >> stream redirects are more than enough.
Your command will work if you make sure the input file has at least one line:
[ "$(wc -l < test)" -gt 0 ] || printf '\n' >> test
sed -i -e '1 i/etc/example/live/example.com/fullchain.pem;\' test
To insert text to the first line and put the rest on a new line using sed on macOS this worked for me
sed -i '' '1 i \
Insert
' ~/Downloads/File-path.txt
First and Last
I would assume that anyone who searched for how to insert/append text to the beginning/end of a file probably also needs to know how to do the other also.
cal | \
gsed -E \
-e '1i\{' \
-e '1i\ "lines": [' \
-e 's/(.*)/ "\1",/' \
-e '$s/,$//' \
-e '$a\ ]' \
-e '$a\}'
Explanation
This is cal output piped to gnu-sed (called gsed on macOS installed via brew.sh) with extended RegEx (-E) and 6 "scripts" applied (-e) and line breaks escaped with \ for readability. Scripts 1 & 2 use 1i\ to "at line 1, insert". Scripts 5 & 6 use $a\ to "at line <last>, append". I vertically aligned the text outputs to make the code represent what is expected in the result. Scripts 3 & 4 do substitutions (the latter applying only to "line <last>"). The result is converting command output to valid JSON.
output
{
"lines": [
" October 2019 ",
"Su Mo Tu We Th Fr Sa ",
" 1 2 3 4 5 ",
" 6 7 8 9 10 11 12 ",
"13 14 15 16 17 18 19 ",
"20 21 22 23 24 25 26 ",
"27 28 29 30 31 ",
" "
]
}
For help getting this to work with the macos/BSD version of sed, see my answer here.

Awk script to match a pattern and then remove entire line after delimiter

I have a file which has several lines with alphanumeric strings like ZINC123345667_123 followed by other lines. Now, I need to remove the digits after the delimiter "_" in only lines with strings containing "ZINC" and rest of the other lines remaining the same. I have tried using the below awk command, but obtained only the lines with "ZINC" and not with other lines.
My original Data:
Name: ZINC00000036_1
Grid Score: -23.170839
Grid_vdw: -22.304409
Grid_es: -0.866430
Int_energy: 4.932559
#<TRIPOS>MOLECULE
ZINC00000036_1
18 18 1 0 0
Name: ZINC00000053_3
Grid Score: -23.739523
Grid_vdw: -22.876204
Grid_es: -0.863320
Int_energy: 9.981080
#<TRIPOS>MOLECULE
ZINC00000053_3
20 20 1 0 0
Name: ZINC00000351_12
Grid Score: -30.763229
Grid_vdw: -27.735493
Grid_es: -3.027738
Int_energy: 4.097543
#<TRIPOS>MOLECULE
ZINC00000351_12
31 31 1 0 0
I have executed the below awk script
awk -F'_' '/ZINC/ {print $1}' data.file > out.file
Output obtained:
Name: ZINC00000036
ZINC00000036
Name: ZINC00000053
ZINC00000053
Name: ZINC00000351
ZINC00000351
But, I need the other lines too in the output file as below:
Name: ZINC00000036
Grid Score: -23.170839
Grid_vdw: -22.304409
Grid_es: -0.866430
Int_energy: 4.932559
#<TRIPOS>MOLECULE ZINC00000036 18 18 1 0 0
Name: ZINC00000053
Grid Score: -23.739523
Grid_vdw: -22.876204
Grid_es: -0.863320
Int_energy: 9.981080
#<TRIPOS>MOLECULE ZINC00000053 20 20 1 0 0
Name: ZINC00000351
Grid Score: -30.763229
Grid_vdw: -27.735493
Grid_es: -3.027738
Int_energy: 4.097543
#<TRIPOS>MOLECULE ZINC00000351 31 31 1 0 0
As my data file is huge and transforming it would be impossible, I will greatly appreciate any help with awk.
sed '/ZINC/s/_.*//' file
awk '/ZINC/{sub(/_.*/,"")}1' file
I would tackle this with sed:
sed -E '/ZINC[0-9]+_/s/_.*//' yourfile
That says... on any lines that contains "ZINC" followed by some digits then an underscore, substitute (i.e. replace) the underscore and anything else on the line with nothing in yourfile.
If you add -i after the sed command, it allows in-place editing without your needing to create a second file.
I don't think that awk is the right tool for this job. A simple sed command will do it:
sed 's/\(ZINC[0-9]\{1,\}\)_[0-9]\{1,\}/\1/' file # most portable
sed 's/\(ZINC[0-9]\+\)_[0-9]\+/\1/' file # GNU sed
sed -E 's/(ZINC[0-9]+)_[0-9]+/\1/' file # extended regex mode
Capture the part before the underscore (ZINC, followed by some digits) and discard the rest.
Same thing in Perl, which is marginally shorter due to the digit character class \d:
perl -pe 's/(ZINC\d+)_\d+/$1/' file
Come to think of it, if you're determined to use awk, this would work:
awk -F_ '/ZINC/{$0=$1}1' file
When ZINC is matched, overwrite the line with the contents of the first field. The 1 at the end ensures that every line is printed.
To keep only the part before the first underscore character _ on lines containing ZINC, and leave the other lines in tact, you can do:
awk -F'_' '/ZINC/{print $1;next}1' file
Another format of answer using sed,
sed 's/\(ZINC[0-9]*\)\(_.*\)/\1/g' inputfile
Replacing the entire string with the first half of the pattern. Rest all other lines will be displayed

how to edit a line with an extension in one file from the data of another file in linux

Inn my text file I have the following lines.
input.k
has
2684717 -194.7050476 64.2345581 150.6500092 0 0
2684718 -213.1575623 62.7032242 150.6500092 0 0
*INCLUDE
$# filename
./meshes/exportneu/147.k
*END
and
mesh.k
has
100
I want to replace the 147.k in input.k to another number form another file which is 100 in mesh.k
Required output
2684717 -194.7050476 64.2345581 150.6500092 0 0
2684718 -213.1575623 62.7032242 150.6500092 0 0
*INCLUDE
$# filename
../meshes/exportneu/100.k
*END
I used
sed '/\<meshes\>/!d;=;s/.* ([^ ]\+).*/\1/;R mesh.k' input.k |
sed 'N;N;s|\n|s/|;s|\n|/|;s|$|/|;q' >temp.sed
sed -i -f temp.sed input.k
The point is that I want to replace this 147.k to 100.k where 100 is written in another file mesh.k , like in the other file only 100 is present or it could be 3 digit anyother number.
i know it can work with searching the line with word meshes for example and the dividing with last / and piping the data from other file but am not able to formulate the sed or awk.
regards
You can try something like this.
awk 'NR==FNR{a[++i]=$1; next} {{sub(/[0-9]+/,a[++j]); print}}' f2 f1
However, please note that your substitutions from another file needs to be in the same order as your input line that needs the substitution -
$ cat f1
../meshes/exportneu/147.k
../secondline/exportneu/10.k
$ cat f2
100
40
$ awk 'NR==FNR{a[++i]=$1; next} {{sub(/[0-9]+/,a[++j]); print}}' f2 f1
../meshes/exportneu/100.k
../secondline/exportneu/40.k
You can improve upon your substitution inside awk as per your file. This is just to get you in the right direction.

How can I swap two lines using sed?

Does anyone know how to replace line a with line b and line b with line a in a text file using the sed editor?
I can see how to replace a line in the pattern space with a line that is in the hold space (i.e., /^Paco/x or /^Paco/g), but what if I want to take the line starting with Paco and replace it with the line starting with Vinh, and also take the line starting with Vinh and replace it with the line starting with Paco?
Let's assume for starters that there is one line with Paco and one line with Vinh, and that the line Paco occurs before the line Vinh. Then we can move to the general case.
#!/bin/sed -f
/^Paco/ {
:notdone
N
s/^\(Paco[^\n]*\)\(\n\([^\n]*\n\)*\)\(Vinh[^\n]*\)$/\4\2\1/
t
bnotdone
}
After matching /^Paco/ we read into the pattern buffer until s// succeeds (or EOF: the pattern buffer will be printed unchanged). Then we start over searching for /^Paco/.
cat input | tr '\n' 'ç' | sed 's/\(ç__firstline__\)\(ç__secondline__\)/\2\1/g' | tr 'ç' '\n' > output
Replace __firstline__ and __secondline__ with your desired regexps. Be sure to substitute any instances of . in your regexp with [^ç]. If your text actually has ç in it, substitute with something else that your text doesn't have.
try this awk script.
s1="$1"
s2="$2"
awk -vs1="$s1" -vs2="$s2" '
{ a[++d]=$0 }
$0~s1{ h=$0;ind=d}
$0~s2{
a[ind]=$0
for(i=1;i<d;i++ ){ print a[i]}
print h
delete a;d=0;
}
END{ for(i=1;i<=d;i++ ){ print a[i] } }' file
output
$ cat file
1
2
3
4
5
$ bash test.sh 2 3
1
3
2
4
5
$ bash test.sh 1 4
4
2
3
1
5
Use sed (or not at all) for only simple substitution. Anything more complicated, use a programming language
A simple example from the GNU sed texinfo doc:
Note that on implementations other than GNU `sed' this script might
easily overflow internal buffers.
#!/usr/bin/sed -nf
# reverse all lines of input, i.e. first line became last, ...
# from the second line, the buffer (which contains all previous lines)
# is *appended* to current line, so, the order will be reversed
1! G
# on the last line we're done -- print everything
$ p
# store everything on the buffer again
h

grep: show lines surrounding each match

How do I grep and show the preceding and following 5 lines surrounding each matched line?
For BSD or GNU grep you can use -B num to set how many lines before the match and -A num for the number of lines after the match.
grep -B 3 -A 2 foo README.txt
If you want the same number of lines before and after you can use -C num.
grep -C 3 foo README.txt
This will show 3 lines before and 3 lines after.
-A and -B will work, as will -C n (for n lines of context), or just -n (for n lines of context... as long as n is 1 to 9).
ack works with similar arguments as grep, and accepts -C. But it's usually better for searching through code.
grep astring myfile -A 5 -B 5
That will grep "myfile" for "astring", and show 5 lines before and after each match
ripgrep
If you care about the performance, use ripgrep which has similar syntax to grep, e.g.
rg -C5 "pattern" .
-C, --context NUM - Show NUM lines before and after each match.
There are also parameters such as -A/--after-context and -B/--before-context.
The tool is built on top of Rust's regex engine which makes it very efficient on the large data.
I normally use
grep searchstring file -C n # n for number of lines of context up and down
Many of the tools like grep also have really great man files too. I find myself referring to grep's man page a lot because there is so much you can do with it.
man grep
Many GNU tools also have an info page that may have more useful information in addition to the man page.
info grep
Use grep
$ grep --help | grep -i context
Context control:
-B, --before-context=NUM print NUM lines of leading context
-A, --after-context=NUM print NUM lines of trailing context
-C, --context=NUM print NUM lines of output context
-NUM same as --context=NUM
If you search code often, AG the silver searcher is much more efficient (ie faster) than grep.
You show context lines by using the -C option.
Eg:
ag -C 3 "foo" myFile
line 1
line 2
line 3
line that has "foo"
line 5
line 6
line 7
Search for "17655" in /some/file.txt showing 10 lines context before and after (using Awk), output preceded with line number followed by a colon. Use this on Solaris when grep does not support the -[ACB] options.
awk '
/17655/ {
for (i = (b + 1) % 10; i != b; i = (i + 1) % 10) {
print before[i]
}
print (NR ":" ($0))
a = 10
}
a-- > 0 {
print (NR ":" ($0))
}
{
before[b] = (NR ":" ($0))
b = (b + 1) % 10
}' /some/file.txt;
Let's understand using an example.
We can use grep with options:
-A 5 # this will give you 5 lines after searched string.
-B 5 # this will give you 5 lines before searched string.
-C 5 # this will give you 5 lines before & after searched string
Example.
File.txt contains 6 lines and following are the operations.
[abc#xyz]~/% cat file.txt # print all file data
this is first line
this is 2nd line
this is 3rd line
this is 4th line
this is 5th line
this is 6th line
[abc#xyz]~% grep "3rd" file.txt # we are searching for keyword '3rd' in the file
this is 3rd line
[abc#xyz]~% grep -A 2 "3rd" file.txt # print 2 lines after finding the searched string
this is 3rd line
this is 4th line
this is 5th line
[abc#xyz]~% grep -B 2 "3rd" file.txt # Print 2 lines before the search string.
this is first line
this is 2nd line
this is 3rd line
[abc#xyz]~% grep -C 2 "3rd" file.txt # print 2 line before and 2 line after the searched string
this is first line
this is 2nd line
this is 3rd line
this is 4th line
this is 5th line
Trick to remember options:
-A  → A means "after"
-B  → B means "before"
-C  → C means "in between"
I do it the compact way:
grep -5 string file
That is the equivalent of:
grep -A 5 -B 5 string file
Here is the #Ygor solution in awk
awk 'c-->0;$0~s{if(b)for(c=b+1;c>1;c--)print r[(NR-c+1)%b];print;c=a}b{r[NR%b]=$0}' b=3 a=3 s="pattern" myfile
Note: Replace a and b variables with number of lines before and after.
It's especially useful for system which doesn't support grep's -A, -B and -C parameters.
Grep has an option called Context Line Control, you can use the --context in that, simply,
| grep -C 5
or
| grep -5
Should do the trick
$ grep thestring thefile -5
-5 gets you 5 lines above and below the match 'thestring' is equivalent to -C 5 or -A 5 -B 5.

Resources