Print lines in file from the match line until end of file - sed

I wrote the following awk to print lines from the match line until EOF
awk '/match_line/,/*/' file
How can I do the same in sed?

sed -n '/matched/,$p' file
awk '/matched/,0' file

This is for a really old version of GNU sed on Windows
GNU sed version 2.05
http://www.gnu.org/software/sed/manual/sed.html
-n only display if Printed
-e expression to evaluate
d stands for Delete
p stands for Print
$ end of file
line1,line2 is the range
! is NOT
haystack.txt
abc
def
ghi
needle
want 1
want 2
Print matching line and following lines to end of file
>sed.exe -n -e "/needle/,$p" haystack.txt
needle
want 1
want 2
Print start of file up to BUT NOT including matching line
>sed.exe -n -e "/needle/,$!p" haystack.txt
abc
def
ghi
Print start of file up to AND including matching line
>sed.exe -n -e "1,/needle/p" haystack.txt
abc
def
ghi
needle
Print everything after matching line
>sed.exe -n -e "1,/needle/!p" haystack.txt
want 1
want 2
Print everything between two matches - inclusive
>sed.exe -n -e "/def/,/want 1/p" haystack.txt
def
ghi
needle
want 1
Delete everything between two matches, no -n with this usage
>sed.exe -e "/ghi/,/want 1/d" haystack.txt
abc
def
want 2

Related

Using a single sed call to split and grep

This is mostly by curiosity, I am trying to have the same behavior as:
echo -e "test1:test2:test3"| sed 's/:/\n/g' | grep 1
in a single sed command.
I already tried
echo -e "test1:test2:test3"| sed -e "s/:/\n/g" -n "/1/p"
But I get the following error:
sed: can't read /1/p: No such file or directory
Any idea on how to fix this and combine different types of commands into a single sed call?
Of course this is overly simplified compared to the real usecase, and I know I can get around by using multiple calls, again this is just out of curiosity.
EDIT: I am mostly interested in the sed tool, I already know how to do it using other tools, or even combinations of those.
EDIT2: Here is a more realistic script, closer to what I am trying to achieve:
arch=linux64
base=https://chromedriver.storage.googleapis.com
split="<Contents>"
curl $base \
| sed -e 's/<Contents>/<Contents>\n/g' \
| grep $arch \
| sed -e 's/^<Key>\(.*\)\/chromedriver.*/\1/' \
| sort -V > out
What I would like to simplify is the curl line, turning it into something like:
curl $base \
| sed 's/<Contents>/<Contents>\n/g' -n '/1/p' -e 's/^<Key>\(.*\)\/chromedriver.*/\1/' \
| sort -V > out
Here are some alternatives, awk and sed based:
sed -E "s/(.*:)?([^:]*1[^:]*).*/\2/" <<< "test1:test2:test3"
awk -v RS=":" '/1/' <<< "test1:test2:test3"
# or also
awk 'BEGIN{RS=":"} /1/' <<< "test1:test2:test3"
Or, using your logic, you would need to pipe a second sed command:
sed "s/:/\n/g" <<< "test1:test2:test3" | sed -n "/1/p"
See this online demo. The awk solution looks cleanest.
Details
In sed solution, (.*:)?([^:]*1[^:]*).* pattern matches an optional sequence of any 0+ chars and a :, then captures into Group 2 any 0 or more chars other than :, 1, again 0 or more chars other than :, and then just matches the rest of the line. The replacement just keeps Group 2 contents.
In awk solution, the record separator is set to : and then /1/ regex is used to only return the record having 1 in it.
This might work for you (GNU sed):
sed 's/:/\n/;/^[^\n]*1/P;D' file
Replace each : and if the first line in the pattern space contains 1 print it.
Repeat.
An alternative:
sed -Ez 's/:/\n/g;s/^[^1]*$//mg;s/\n+/\n/;s/^\n//' file
This slurps the whole file into memory and replaces all colons by newlines. All lines that do not contain 1 are removed and surplus newlines deleted.
An alternative to the really ugly sed is: grep -o '\w*2\w*'
$ printf "test1:test2:test3\nbob3:bob2:fred2\n" | grep -o '\w*2\w*'
test2
bob2
fred2
grep -o: only matching
Or: grep -o '[^:]*2[^:]*'
echo -e "test1:test2:test3" | sed -En 's/:/\n/g;/^[^\n]*2[^\n]*(\n|$)/P;//!D'
sed -n doesn't print unless told to
sed -E allows using parens to match (\n|$) which is newline or the end of the pattern space
P prints the pattern buffer up to the first newline.
D trims the pattern buffer up to the first newline
[^\n] is a character class that matches anything except a newline
// is sed shorthand for repeating a match
//! is then matching everything that didn't match previously
So, after you split into newlines, you want to make sure the 2 character is between the start of the pattern buffer ^ and the first newline.
And, if there is not the character you are looking for, you want to D delete up to the first newline.
At that point, it works for one line of input, with one string containing the character you're looking for.
To expand to several matches within a line, you have to ta, conditionally branch back to label :a:
$ printf "test1:test2:test3\nbob3:bob2:fred2\n" | \
sed -En ':a s/:/\n/g;/^[^\n]*2[^\n]*(\n|$)/P;D;ta'
test2
bob2
fred2
This is simply NOT a job for sed. With GNU awk for multi-char RS:
$ echo "test1:test2:test3:test4:test5:test6"| awk -v RS='[:\n]' '/1/'
test1
$ echo "test1:test2:test3:test4:test5:test6"| awk -v RS='[:\n]' 'NR%2'
test1
test3
test5
$ echo "test1:test2:test3:test4:test5:test6"| awk -v RS='[:\n]' '!(NR%2)'
test2
test4
test6
$ echo "foo1:bar1:foo2:bar2:foo3:bar3" | awk -v RS='[:\n]' '/foo/ || /2/'
foo1
foo2
bar2
foo3
With any awk you'd just have to strip the \n from the final record before operating on it:
$ echo "test1:test2:test3:test4:test5:test6"| awk -v RS=':' '{sub(/\n$/,"")} /1/'
test1

Inserting spaces before the word while adding lines using sed

My file content-
abc
def
I want to add two lines after abc with spaces before it
My final file content should be -
abc
123
def
I am using the below command, but not working for me, plz help me
sudo sed -i "/abc/a\\\123" file.txt
Note - There is no space between lines, I just want to put some spaces before the new line (i.e. before the line 123)
You can use this sed:
sed -i '/abc/a\ 123' file
Ex:
$ sed '/abc/a\ 123' file
abc
123
def
Following is awk solution ,if you are open for awk.
Sample input:
cat infile
abc
def
Explanation:
Check for pattern abc if found, update the current line with current line followed by newline followed by 123. And 1 invokes awk's default action of printing.
note : Newline is printed using awk's inbuilt variable called ORS ,which is default set to newline.
awk '/abc/ {$0=$0 ORS " 123" }1' infile
abc
123
def
To make changes in orignal file:
awk '/abc/ {$0=$0 ORS " 123" }1' infile >infile.tmp && mv infile.tmp infile
Just use awk for clarity, portability, etc.:
awk '{print} /abc/{print " 123"}' file

SED: Move multiple lines to the end of a text file

I want to move multiple lines using SED to the end of a file. For example:
ABCDEF
GHIJKL
MNOPQR
STUVWX
YZ1234
should become:
STUVWX
YZ1234
ABCDEF
GHIJKL
MNOPQR
The question asks a method for using sed to move multiple lines. Although the question, as currently edited, does not show multiple lines, I will assume that they are actually meant to be separate lines.
To use sed to move the first three lines to the end:
$ cat file
ABCDEF
GHIJKL
MNOPQR
STUVWX
YZ1234
$ sed '1,3{H;d}; ${p;x;s/^\n//}' file
STUVWX
YZ1234
ABCDEF
GHIJKL
MNOPQR
Explanation:
1,3{H;d}
The 1,3 restricts these commands to operation only on lines 1 through 3. H tells sed to save the current line to the hold buffer. d tells sed not to print the current line at this time.
${p;x;s/^\n//}
The $ restricts this command to the last line. The p tells sed to print the last line. x exchanges the pattern buffer and hold buffer. The lines that we saved from the beginning of the file are now in the ready to be printed. Before printing, though, we remove the extraneous leading newline character.
Before continuing to the next line, sed will print anything left in the pattern buffer.
If you are on Mac OSX or other BSD platform, try:
sed -e '1,3{H;d;}' -e '${p;x;s/^\n//;}' file
Using head and tail
If you are already familiar with head and tail, this may be a simpler way of moving the first three lines to the end:
$ tail -n+4 file; head -n3 file
STUVWX
YZ1234
ABCDEF
GHIJKL
MNOPQR
Alternative sed command
In the comments, potong suggests:
$ sed '1,3{1h;1!H;d};$G' file
STUVWX
YZ1234
ABCDEF
GHIJKL
MNOPQR
The combination of h, H, and G commands eliminate the need for a substitution command to remove the extra newline.
Try this using awk
awk '{print $4,$5,$1,$2,$3}' file
STUVWX YZ1234 ABCDEF GHIJKL MNOPQR
PS, this is multiple fields, not lines.
To write it back to the original file
awk '{print $4,$5,$1,$2,$3}' file > tmp && mv tmp file
If data is in this format:
cat file
ABCDEF
GHIJKL
MNOPQR
STUVWX
Then this may do:
awk '{a[NR]=$0} END {print a[4],a[5],a[1],a[2],a[3]}' OFS="\n" file
STUVWX
YZ1234
ABCDEF
GHIJKL
MNOPQR
Simple ex should do:
ex file <<< $'1,3m$\nw'

Sed or awk: how to call line addresses from separate file?

I have 'file1' with (say) 100 lines. I want to use sed or awk to print lines 23, 71 and 84 (for example) to 'file2'. Those 3 line numbers are in a separate file, 'list', with each number on a separate line.
When I use either of these commands, only line 84 gets printed:
for i in $(cat list); do sed -n "${i}p" file1 > file2; done
for i in $(cat list); do awk 'NR==x {print}' x=$i file1 > file2; done
Can a for loop be used in this way to supply line addresses to sed or awk?
This might work for you (GNU sed):
sed 's/.*/&p/' list | sed -nf - file1 >file2
Use list to build a sed script.
You need to do > after the loop in order to capture everything. Since you are using it inside the loop, the file gets overwritten. Inside the loop you need to do >>.
Good practice is to or use > outside the loop so the file is not open for writing during every loop iteration.
However, you can do everything in awk without for loop.
awk 'NR==FNR{a[$1]++;next}FNR in a' list file1 > file2
You have to >>(append to the file) . But you are overwriting the file. That is why, You are always getting 84 line only in the file2.
Try use,
for i in $(cat list); do sed -n "${i}p" file1 >> file2; done
With sed:
sed -n $(sed -e 's/^/-e /' -e 's/$/p/' list) input
given the example input, the inner command create a string like this: `
-e 23p
-e 71p
-e 84p
so the outer sed then prints out given lines
You can avoid running sed/awk in a for/while loop altgether:
# store all lines numbers in a variable using pipe
lines=$(echo $(<list) | sed 's/ /|/g')
# print lines of specified line numbers and store output
awk -v lineS="^($lines)$" 'NR ~ lineS' file1 > out

Sed replace pattern with line number

I need to replace the pattern ### with the current line number.
I managed to Print in the next line with both AWK and SED.
sed -n "/###/{p;=;}" file prints to the next line, without the p;, it replaces the whole line.
sed -e "s/###/{=;}/g" file used to make sense in my head, since the =; returns the line number of the matched pattern, but it will return me the the text {=;}
What am i Missing? I know this is a silly question. I couldn't find the answer to this question in the sed manual, it's not quite clear.
If possible, point me what was i missing, and what to make it work. Thank you
Simple awk oneliner:
awk '{gsub("###",NR,$0);print}'
Given the limitations of the = command, I think it's easier to divide the job in two (actually, three) parts. With GNU sed you can do:
$ sed -n '/###/=' test > lineno
and then something like
$ sed -e '/###/R lineno' test | sed '/###/{:r;N;s/###\([^\n]*\n\)\([^\n]*\)/\2\1/;tr;:c;s/\n\n/\n/;tc}'
I'm afraid there's no simple way with sed because, as well as the = command, the r and GNU extension R commands don't read files into the pattern space, but rather directly append the lines to the output, so the contents of the file cannot be modified in any way. Hence piping to another sed command.
If the contents of test are
fooo
bar ### aa
test
zz ### bar
the above will produce
fooo
bar 2 aa
test
zz 4 bar
This might work for you (GNU sed):
sed = file | sed 'N;:a;s/\(\(.*\)\n.*\)###/\1\2/;ta;s/.*\n//'
An alternative using cat:
cat -n file | sed -E ':a;s/^(\s*(\S*)\t.*)###/\1\2/;ta;s/.*\t//'
As noted by Lev Levitsky this isn't possible with one invocation of sed, because the line number is sent directly to standard out.
You could have sed write a sed-script for you, and do the replacement in two passes:
infile
a
b
c
d
e
###
###
###
a
b
###
c
d
e
###
Find the lines that contain the pattern:
sed -n '/###/=' infile
Output:
6
7
8
11
15
Pipe that into a sed-script writing a new sed-script:
sed 's:.*:&s/###/&/:'
Output:
6s/###/6/
7s/###/7/
8s/###/8/
11s/###/11/
15s/###/15/
Execute:
sed -n '/###/=' infile | sed 's:.*:&s/^/& \&/:' | sed -f - infile
Output:
a
b
c
d
e
6
7
8
a
b
11
c
d
e
15
is this ok ?
kent$ echo "a
b
c
d
e"|awk '/d/{$0=$0" "NR}1'
a
b
c
d 4
e
if match pattern "d", append line number at the end of the line.
edit
oh, you want to replace the pattern not append the line number... take a look the new cmd:
kent$ echo "a
b
c
d
e"|awk '/d/{gsub(/d/,NR)}1'
a
b
c
4
e
and the line could be written like this as well: awk '1+gsub(/d/,NR)' file
one-liner to modify the FILE in place, replacing LINE with the corresponding line number:
seq 1 `wc -l FILE | awk '{print $1}'` | xargs -IX sed -i 'X s/LINE/X/' FILE
Following on from https://stackoverflow.com/a/53519367/29924
If you try this on osx the version of sed is different and you need to do:
seq 1 `wc -l FILE | awk '{print $1}'` | xargs --verbose -IX sed -i bak "X s/__line__/X/" FILE
see https://markhneedham.com/blog/2011/01/14/sed-sed-1-invalid-command-code-r-on-mac-os-x/