PDA

View Full Version : sed one line to another



maclenin
March 14th, 2016, 10:53 PM
"Why might this be happening?"

I have (moved) appended one line...

["<p class = \"a\">one line</p>"];
...to another...

["<p class = \"b\">another</p>"];
...within a javascript file, hoping to yield:

["<p class = \"b\">another</p>"];
["<p class = \"a\">one line</p>"];
However, this is the result I see:

["<p class = \"b\">another</p>"];
["<p class = "a">one line</p>"];
Note the missing \ and \ in the appended one line.

I have run a sed statement similar to...

sed -e '/^.*another.*$/ a\'$'\n'"${one_line}" < source > target
...to execute the move.

I captured one line inside a variable using:

one_line=$(sed -n -e '/^.*one line.*$/p' < source)
Running...

echo "${one_line}"
...yields...

["<p class = \"a\">one line</p>"];
...with \ and \ included.

Hmm? http://www.grymoire.com/Unix/Sed.html#uh-62

Thanks for any guidance!

papibe
March 14th, 2016, 11:30 PM
Hi maclenin.

I believe you are trying to use a string as a regular expression. I think it would work if you escape the 'escapers'

Try this:

ol_expr="${one_line//\\/\\\\}"

sed -e '/^.*another.*$/ a\'$'\n'"${ol_expr}" < source
Or this:

sed -e '/^.*another.*$/ a\'$'\n'"${one_line//\\/\\\\}" < source
Hope it helps. Let us know how it goes.
Regards.

maclenin
March 14th, 2016, 11:56 PM
papibe!

Thank you!

\[SOLVED\] indeed!

Can you tell me why that particular sequence / critical mass / method of escaper escaping did the trick?

Thanks, again!

P.S. I think one leading / is missing from your "Or this:" solution!

steeldriver
March 14th, 2016, 11:57 PM
If you don't have to stream the source file via stdin, another option might be to use the 'r' command



sed -e '/another/ r /dev/stdin' <<< "$one_line" source


You could probably combine them e.g.



sed '/one line/!d' source | sed -e '/another/ r /dev/stdin' source

papibe
March 15th, 2016, 01:42 AM
I think one leading / is missing from your "Or this:" solution!
Fixed! Thanks :D


Can you tell me why that particular sequence / critical mass / method of escaper escaping did the trick?
Backslash works as an escape character in a regular expression. This create the problem of how to reference the backslash itself. The solution is escape it too ;)

$ echo 'hello\ world' | sed -e 's/\//'
sed: -e expression #1, char 5: unterminated `s' command

$ echo 'hello\ world' | sed -e 's/\\//'
hello world

Does that help?
Regards.

maclenin
March 15th, 2016, 09:23 AM
steeldriver!

Thanks for the r - I'll take a closer look....

papibe!

Thank you - your latest example is very clear: sed statement with free-roaming \ gumming up the works, with second \ introduced to escape the previously free-roaming \

In your "Or this:" example (for example), I am trying to wrap my head around the structure and logic of this specific herd of (forward and) backslashes - //\\/\\\\ - and how it all escapes its way into the ${one_line} string. Are there delimiters / in the herd of \ or simply backslashes escaping!?

I count 3 / and 6 \ in the herd. What do they each do?

Thanks, again, for any additional clarity!

papibe
March 15th, 2016, 08:47 PM
This:

${one_line//\\/\\\\}"
is what some called string replacement, parameter expansion, or parameter substitution.

Here's a basic example:

$ var="yabadabadoo"

$ echo "$var"
yabadabadoo

$ echo "${var}"
yabadabadoo

$ echo "${var/a/A}"
yAbadabadoo

$ echo "${var//a/A}"
yAbAdAbAdoo


So, in words this:

${one_line//\\/\\\\}"
would be:

Take the variable called one_line
do a string replacement for all occurrences of the string '\', a backslash (which has to be refer as '//'), and
replace it with '\\', two backslashes (refer as '\\\\' because of the same reason).

Hope it helps.
Regards.

EDIT: here are some details on the matter: Shell parameter expansion (https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html), String Manipulation (go down to Substring Replacement) (http://tldp.org/LDP/abs/html/string-manipulation.html), and Parameter Substitution (http://tldp.org/LDP/abs/html/parameter-substitution.html).

maclenin
March 16th, 2016, 12:22 AM
papibe!

It helps (again) - indeed! Thank you and for the additional detail! I think I'd been trying to see a regular expression or sed syntax in the delimiters \ & / escapes and had overlooked the (ba)shell!

Relatedly - one other sed question (if I may!)....

Is it possible to use a line number as substitution for / reference to the content of that line (other than, say, 3 in sed -n '3p')?

For example (a hypothetical)...


sed -e '1 a\'$'\n'=2 < source > target

...where:

1 is the number of the line in source to which we are appending
2 is the number of the line in source whose content or /pattern/ we are (moving &) appending

It is possible - via...

sed -n '/pattern/='
...to retrieve the line number of the pattern - let's say 2. However, can the line number 2 be used, in reverse, as reference to the /pattern/ - as shown in my example?

Thanks, again, on all fronts!

steeldriver
March 16th, 2016, 12:40 AM
You mean something like

Given


$ cat source
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6


then to move line 5 after line 2


$ sed '5!d' source | sed -e '5d' -e '2r /dev/stdin' source
Line 1
Line 2
Line 5
Line 3
Line 4
Line 6


EDIT: if you only want to move lines DOWN, then you can do that much more simply e.g. copy line m to the hold space and delete it, then append the hold space to the pattern space at line n



$ sed '2{h;d};5G' source
Line 1
Line 3
Line 4
Line 5
Line 2
Line 6

maclenin
March 16th, 2016, 04:35 PM
steeldriver!

Perfect! You folks have been very helpful!

Just to distil the mission a bit (with a tiny wrinkle)....

source: a file which can have a varying quantity of lines


$ cat source
line one
line two
line three
line four
line five
line six
line seven
line eight
line nine
line ten
$
task: if certain lines exist, move certain lines

$ s=$(sed -n '/seven/,${/eight/=;}' < source)
$ echo "${s}"
8
$ t=$(sed -n '/\(three\|four\)/=' < source)
$ echo "${t}"
3
4
$ if [ -z "${s}" ] && [ -z "${t}" ]; then echo "no match!"; else sed ''"${t}"'{h;d;};'"${s}"'G' < source > target; fi
sed: -e expression #1, char 2: unknown command: `
'
$

target: this is the expected result (given the source), however, I smell a multi-line match (handling) error, which I am not certain how to address....


$ cat target
line one
line two
line five
line six
line seven < --- this line existed
line eight < --- this line existed
line three < --- this line was moved
line four < --- this line was moved
line nine
line ten
$
Note: If I were to change the t variable to match line /three/ only - all works for a single-line match - line three is moved....

I have switched to line numbers as the match reference method to escape having to "escape the 'escapers'"....

Thanks, again, for the guidance!

maclenin
March 18th, 2016, 08:39 AM
...to clarify:

single-line match and move...

$ s=$(sed -n '/seven/,${/eight/=;}' < source)
$ t=$(sed -n '/three/=' < source)
$ if [ -z "${s}" ] && [ -z "${t}" ]; then echo "no match!"; else sed ''"${t}"'{h;d;};'"${s}"'G' < source > target; fi
... (runs error-free and) yields:

$ cat target
line one
line two
line four
line five
line six
line seven < --- this line exists
line eight < --- this line exists
line three < --- this line exists and was moved
line nine
line ten
$
multi-line match and move...

$ s=$(sed -n '/seven/,${/eight/=;}' < source)
$ t=$(sed -n '/\(three\|four\)/=' < source)
$ if [ -z "${s}" ] && [ -z "${t}" ]; then echo "no match!"; else sed ''"${t}"'{h;d;};'"${s}"'G' < source > target; fi
... (however) yields:

sed: -e expression #1, char 2: unknown command: `
'
$
I am working to untangle the multi-line match and move unknown command issue.

Thanks for any help.

maclenin
March 18th, 2016, 08:20 PM
Nearly there (but for a stray newline)...

...and in a (portable*) nutshell:



$ cat source
line one
line two
line three
line four
line five
line six
line seven
line eight
line nine
line ten
$ se=$(sed -n '/seven/,${/eight/=;}' < source)
$ tf=$(sed -E -n '/(three|four)/=' < source | paste -s -d, -)
$ printf '%s\n' "${tf}"
3,4
$ if [ ! -z "${se}" ] && [ ! -z "${tf}" ]; then sed "${tf}{H;d;};${se}G" < source > target; fi
$ cat -A target
line one$
line two$
line five$
line six$
line seven$
line eight$
$ < --- (how) can this newline be suppressed? or is sed '/^[[:space:]]*$/d' after the fact the best way do away with this line?
line three$
line four$
line nine$
line ten$
$

Notes:
1. -E(xtended) switch added to allow (mac) alternation (a|b). Swtich has the same -E(xtended) effect as -r in (my experience) with gnu sed 4.2.2 (xubuntu)....
2. Multi-line, newline-delimited (numeric) string (tf) needed to be converted to a comma-delimited string (using paste) in order for both line numbers to be (interpreted / read and) separately added (match #1) and appended (match #2) to the hold buffer.
3. h changed to H to permit match #2 to be appended to the hold buffer, with G appending the (multi-line, in my case) hold buffer to the pattern space (after se).

* cat -A target ~ cat -e target on a mac < --- the only non-portable bit, I believe....

This is my take...thanks for any thoughts (or newline suppression tactics).