Replace game1 with game001 with sed nested commands - sed

cat 1.txt | sed -E 's,game([0-9]+),game$(printf %03d \1),g'
to replace 1.txt from:
game1 xxx vs yyy
game11 aaa vs bbb
to:
game001 xxx vs yyy
game011 aaa vs bbb
but the result is:
$ echo "game1 xxx vs yyy" | sed -E 's,game([0-9]+),game$(printf %03d \1),g'
game$(printf %03d 1) xxx vs yyy
How to make printf %03d \1 correctly evaluated?

You need to use double quotes when you need substitution
sed -E "s,game([0-9]+),game$(printf %03d \1),g" 1.txt
Edit:
And, I don't think sed can pass value of \1 to external commands. perl can help in this case:
$ cat 1.txt
game1 xxx vs yyy
game11 aaa vs bbb
game21 aaa vs bbb
$ sed -E "s,game([0-9]+),game$(printf %03d \1),g" 1.txt
game001 xxx vs yyy
game001 aaa vs bbb
game001 aaa vs bbb
$ # can also use: perl -pe 's/game\K\d+/sprintf "%03d", $&/ge'
$ perl -pe 's/game([0-9]+)/sprintf "game%03d", $1/ge' 1.txt
game001 xxx vs yyy
game011 aaa vs bbb
game021 aaa vs bbb

You can't combine shell commands and sed backreferences like this (and if you could, you'd have to double quote the sed command, see other answer). The shell would try to evaluate the command before sed sees it, but \1 wouldn't mean anything to the shell.
You can do it as follows, though:
$ sed -E 's/^(game)([[:digit:]]+)/\100\2/;s/^(game).{0,2}([[:digit:]]{3})/\1\2/' 1.txt
game001 xxx vs yyy
game011 aaa vs bbb
The first substitution, s/^(game)([[:digit:]]+)/\100\2/, adds two zeros in front of the digits after game:
$ sed -E 's/game([[:digit:]]+)/game00\1/' 1.txt
game001 xxx vs yyy
game0011 aaa vs bbb
The second substitution, s/^(game).{0,2}([[:digit:]]{3})/\1\2/ removes up to two characters between game and three digits that follow it, to get rid of unwanted extra zeros.
Notice that
I've used / instead of , as delimiter, just because I'm more used to it.
I've anchored game at the start of the line with ^.
I've used one more capture group for game so I don't have to type it twice per command.
I've used the POSIX character class [[:digit:]] instead of [0-9].
I've used sed '<command>' 1.txt instead of cat 1.txt | sed '<command>' to avoid the useless use of cat.

Just use awk:
$ awk '{sub(/game/,""); $1=sprintf("game%03d",$1)} 1' file
game001 xxx vs yyy
game011 aaa vs bbb
or in general to operate on saved capture groups with GNU awk for the 3rd arg to match():
$ awk 'match($0,/(game)([0-9]+)(.*)/,a){ printf "%s%03d%s\n", a[1], a[2], a[3] }' file
game001 xxx vs yyy
game011 aaa vs bbb
With sed you'd need:
$ sed -E 's/(game)([0-9]) /\10\2 /; s/(game)([0-9]{2}) /\10\2 /' file
game001 xxx vs yyy
game011 aaa vs bbb

Related

combine sed d and sed a scripts to a single script

file xz.txt
123
456
789
I want to merge
sed -i '1d' xz.txt
sed -i '1a abc' xz.txt
I tried
sed -i -e '1d' -e '1a abc' xz.txt
expect to get
456
abc
789
but I got
456
789
sed (GNU sed) 4.7
but it doesn't work, any help?
Sed goes line by line, first command 1d - deleted 1st line, 1st line is gone, there is no more 1st line, that is why second command 1a abc didn't work. Here is how it should be:
$ sed '1d; 2a abc' f
456
abc
789
What is going on is that the delete statement automatically ends the processing sequence:
[1addr]a\
text Write text to standard output as described previously (yes there is a new-line here)
[2addr]d: Delete the pattern space and start the next cycle
Source: Posix)
As the a command does not modify the pattern space but just writes to stdout, you can simply do
[POSIX]$ sed -e '1a\
abc' -e '1d'
[GNU]$ sed -e '1a abc' -e '1d'
However, the easiest is just to use the replace command c:
[POSIX]$ sed -e '1c\
abc'
[GNU]$ sed -e '1c abc`
Note: The reason the commands a and c write directly to the output and not to the pattern space is most likely that it would mess up the address ranging using line-numbers.

How to make sed take input from pipe, and insert into a file

is it possible to use the pipe to redirect the output of the previous command, to sed, and let sed use this as input(pattern or string) to access a file?
I know if you only use sed, you can use something like
sed -i '1 i\anything' file
But can I do something like
head -1 file1 | sed -i '1 i\OutputFromPreviousCmd' file2
This way, I don't need to manually copy the output and change the sed command everytime
Update:
Added the files I meant
head -3 file1.txt
Side A,Age(us),mm:ss.ms_us_ns_ps
84 Vendor Specific, 0000000009096, 0349588242
84 Vendor Specific, 0000000011691, 0349591828
head -3 file2.txt
84 Vendor Specific, 0000000000418, 0349575322
83 Vendor Specific, 0000000002099, 0349575343
83 Vendor Specific, 0000000001628, 0349576662
I'd like to grab the first line of file1 and insert it to file2, so the result should be :
head -3 file2.txt
Side A,Age(us),mm:ss.ms_us_ns_ps
84 Vendor Specific, 0000000000418, 0349575322
83 Vendor Specific, 0000000002099, 0349575343
83 Vendor Specific, 0000000001628, 0349576662
head -1 file1 | sed '1s/^/1i /' | sed -i -f- file2
This takes your one line of output, prepends the sed 1i command, the pipes that sed command stream to sed using -f- to take sed commands from stdin.
For example:
$ echo bob > bob.txt
$ echo alice | sed '1s/^/1i /' | sed -i -f- bob.txt
$ more bob.txt
alice
bob
This looks like pipes and not commands ending in > temp ; mv temp file2, but sed is doing that nonetheless when -i is used.
This might work for you (GNU sed):
head -1 file1 | sed -i '1e cat /dev/stdin' file2
Insert the first line of file1 into the start of file2.
But why not use cat?:
cat <(head -1 file1) file2

[[:space:]] is not matching newline in sed

I am trying to replace all words "BBB" ins a file to "XXX" But for some reason I cant seem to make [[:space:]] match the newline:
[root#REDHAT]# cat file
AAA
BBB
BBB
CCC
[root#REDHAT]# sed 's/[[:space:]]BBB/XXX/g' file
AAA
BBB
XXX
CCC
Note how only the second BBB was replaced; [[:space:]] didn't match the newline preceding the first occurrence.
As Sundeep points out:
sed reads its input line by line by default.
Each line read doesn't include the trailing newline,
so the only thing [[:space:]] can possibly match is a space or a tab char.
Perhaps the following command does what you want (works with BSD Sed and GNU Sed):
$ sed -E 's/(^|[[:blank:]])BBB/XXX/' file
AAA
XXX
XXX
CCC
(^|[[:blank:]]) matches either the beginning of a line (^) or a single tab or space character ([[:blank:]]).
I've omitted the g option, under the assumption that there's at most one BBB per line.

How to force sed to print what it does with my file?

How to force sed to print what it does with my file?
My text01.txt file:
aaa
bbb
ccc
ddd
c
ee
My code:
sed -i 's/c/X/g' ./text01.txt
I want to get in terminal something like this:
sed: line 3 change ccc to XXX
sed: line 5 change c to X
sed -i"bak" 's/c/X/g' text01.txt && diff text01.txt text01.txtbak
will give you a diff summary. like:
3c3
< XXX
---
> ccc
5c5
< X
---
> c
You can read diff man page, to adjust the diff output, e.g. with -c/-u/-y... options as you like.
If you want to get exactly same format you described, you can do some work on diff output as well.
This comes pretty close to your requirement:
$ paste <(cat -n text01.txt) <(sed 's/c/X/g' ./text01.txt)
1 aaa aaa
2 bbb bbb
3 ccc XXX
4 ddd ddd
5 c X
6 ee ee
cat -n prepends line numbers, and the paste command with process substitution prints the file and the sed output next to each other.
Or, more elaborate, with awk:
awk '{ getline mod_line < ARGV[2]
if ($0 != mod_line) {
printf "sed line %d change %s to %s\n", NR, $0, mod_line }
}' text01.txt <(sed 's/c/X/g' text01.txt)
This reads, for each line of text01.txt, the corresponding line as modified by sed. If they are different, the line number and both lines get printed:
sed line 3 change ccc to XXX
sed line 5 change c to X
plus an awk warning because it tries to close an anonymous pipe – this can be suppressed by redirecting stderr, i.e., appending 2> /dev/null to the command.
The closest thing to sed "built-in" debugging is the l command, which prints the current content of the pattern space. If you'd like to go all in, there are proper debuggers, for example sedsed.
This might work for you (GNU sed):
sed -i -e 'h;/c/!b;s//X/g;H;x;s/\n/ to /;s/^/sed: changed /w/dev/stdout' -e 'x' file
This makes a copy of each line in the hold space (HS) and if the substitution pattern does not match, no further action takes place. Otherwise, the substitution is made on the line in the pattern space (PS) and this is appended to the HS. Focus is then changed to the HS and format of before and after effected. The formated line is then written out to the standard output i.e. the terminal and finally focus is reverted to the PS so that the substituted line is included in the original updated file.

Using sed selectively to delete lines

I have a text file (say file)
Name
aaa
bbb
ccc
Name
xxxx
Name
yyyy
tttt
I want to remove "Name" from the file except if it occurs in the header. I know sed removes lines, but if I do
sed '/Name/d' file
it removes all "Name".
Desired ouput:
Name
aaa
bbb
ccc
xxxx
yyyy
tttt
Can you suggest what options I should use?
Use this:
sed '1!{/Name/d}' file
The previous command applies to all lines except of the first line.
If you know that the first header is on the first line, skip it like this:
sed '1!{/Name/d}' infile
That means the pattern should apply on all lines except line 1.
Or the other way around:
sed -n '2,${/Name/d};p' infile
Perhaps with awk:
awk '/Name/ && c++ == 0 || !/Name/' infile
Output in all cases:
Name
aaa
bbb
ccc
xxxx
yyyy
tttt
You might find the awk syntax more intuitive:
awk 'NR==1 || !/Name/' file
the above just says if it's line number 1 or the line doesn't include "Name" then print it