0

I am using Arch which was updated a couple of weeks ago. Packages is use are

  • Arch 6.0.10-arch2-1
  • BASH 5.1.16(1)-release
  • gnu sed 4.9
  • gnu grep 3.8
  • openbox 3.6.1

Within my Openbox rc.xml text file I have two lines as follows

<!-- <keybind key="Right"> -->

<keybind key="s-Up">

I wish to frequently toggle/swap the above two lines for the below two lines using a script

<!-- <keybind key="s-Up"> -->

<keybind key="Right">

This allows me to quickly change a keybinding without tediously editing my keybindings set-up text file (rc.xml) every time.

The script I have so far is below, and is not working, though I know it is close.

I'm not too bothered how this toggling is achieved, but having spent a significant amount of time already on this it would be nice to get below script working.

The sed expressions below that do the text swapping work as they should.

The if statement seems to do the first sed swap if the condition is met but not the second sed swap if the condition is not met, which is the issue.

var_a=""

#var_a=$(grep -zoP  "<\!\-\- <keybind key=\"Right\"> \-\->\n\n<keybind key=\"s-Up\">" /home/kes/Dropbox/lubuntu-rc.xml | sed ':a;N;$!ba;s|\n\n||g')

var_a=$(grep -zoP  "<\!\-\- <keybind key=\"Right\"> \-\->\n\n<keybind key=\"s-Up\">" /home/kes/Dropbox/lubuntu-rc.xml | tr -d '\n' )

# result of grep is
# <!-- <keybind key="Right"> --><keybind key="s-Up">

echo $var_a; sleep 0.5

#if [[ -z ! "$var_a" ];then
if [[ '$var_a'=='<!-- <keybind key="Right"> --><keybind key="s-Up">' ]]; then

   sed -ie ':a;N;$!ba;s|<!-- <keybind key="Right"> -->\n\n<keybind key="s-Up">|<!-- <keybind key="s-Up"> -->\n\n<keybind key="Right">|g' /home/kes/Dropbox/lubuntu-rc.xml

else

   sed -ie ':a;N;$!ba;s|<!-- <keybind key="s-Up"> -->\n\n<keybind key="Right">|<!-- <keybind key="Right"> -->\n\n<keybind key="s-Up">|g' /home/kes/Dropbox/lubuntu-rc.xml

fi
12
  • 1
    I removed the tags as the question is about text-processing, not about the sed, grep or test utilities. Commented Dec 5, 2022 at 13:32
  • 1
    It's not valid XML Commented Dec 5, 2022 at 13:32
  • 1
    @Kusalananda The OP did choose the grep and sed tags for a reason. Future users will likely choose the same tags for a similar problem, so the original tags could help them find the question. The xml and text-processing tags are a good addition, but not replacement, I think. It's similar to X-Y problems, where you better not remove the X-related tags. Commented Dec 5, 2022 at 15:57
  • 1
    @Kusalananda I think Philippos is right. The OP is concerned with getting sed & grep & the bash if statement to process text. It could be any text, even if it is xml. xml I think is less relevant to the question. In fact to me it is more or less irrelevant, becasue the core of this is about text replacement. Only a portion of the xml text within a valid xml tag structure needs to be replaced. For all the escapes etc was a relitavley complicated problem to face. Therefore I kept it as simple as possible with the least amount necessary to process. Solve the simplest problem first Commented Dec 5, 2022 at 20:09
  • 1
    @Kusalananda since the OP uses those tools I can't by any reasonable means see how you reach that conclusion. I think you have deleted relevant tags without any good reason. The OP script uses those tools, right? Commented Dec 5, 2022 at 23:10

2 Answers 2

1

Actually, the condition will never be met, because the single quotes in '$var_a' will prevent your variable from getting expanded at all, so the literal string $var_a will never be equal to th other literal string.

Use double quotes and it should work, but I'd like to add some remarks about your script:

(1) You first do a grep, then base a condition on the contents you get from this grep and finally use two different sed script. This is likely to result in errors. Actually, your sed will do the whole thing alone:

sed -ie ':a;N;$!ba
  s|<!-- <keybind key="Right"> -->\n\n<keybind key="s-Up">|<!-- <keybind key="s-Up"> -->\n\n<keybind key="Right">|g
  t
  s|<!-- <keybind key="s-Up"> -->\n\n<keybind key="Right">|<!-- <keybind key="Right"> -->\n\n<keybind key="s-Up">|g' \
  /home/kes/Dropbox/lubuntu-rc.xml

If the first replacement was made, the t jumps to the end of the script, otherwise the other replacement is performed. The grep and if is already included!

(2) You way to collect all lines :a;N;$!ba will only work in GNU sed, because according to the POSIX standard, everything after the colon will be the jump mark, no exception for ;. No problem for you, because you have GNU sed, but then you also have the -z option to process the hole file in one run without the part. If you like it portable, H;1h;$!d;x does the same thing via the hold space.

(3) I think, the global flag is superfluous, because those lines exist only once, correct?

(4) Writing the same stuff over and over, may cause error and is harder to read. Why not reuse one part by marking it with \(\) and referring to it as \1?

sed -iz 's|<!-- \(<keybind key="\)Right"> -->\n\n\1s-Up">|<!-- \1s-Up"> -->\n\n\1Right">|
  t
  s|<!-- \(<keybind key="\)s-Up"> -->\n\n\1Right">|<!-- \1Right"> -->\n\n\1s-Up">|' \
  /home/kes/Dropbox/lubuntu-rc.xml

(5) Actually, you don't need to process the whole file as one (this can even cause overruns for large files), you only need to process every consecutive three lines together, using the N;P;D scheme (usually two lines together, adding 1N to expand it to three lines):

sed -i '1N;N
  s|<!-- \(<keybind key="\)Right"> -->\n\n\1s-Up">|<!-- \1s-Up"> -->\n\n\1Right">|
  t
  s|<!-- \(<keybind key="\)s-Up"> -->\n\n\1Right">|<!-- \1Right"> -->\n\n\1s-Up">|;
  P;D' /home/kes/Dropbox/lubuntu-rc.xml

One could probably simplify this further, but this would require additional information about the file contents.

8
  • Fantastic answer. Appreciated :) I will come back with more comments Commented Dec 5, 2022 at 11:15
  • (1) I did not know an "if then else" conditional statement coule be done with sed. I have used it. It works as needed. Commented Dec 5, 2022 at 11:17
  • (1) is a remarkable addition to my knowledge Commented Dec 5, 2022 at 11:25
  • (2) I don't know POSIX portability standard well enough. But on a quick try I could not get your suggested substitution to work. I will come back to it later and study it. I presume by the z option you refer to grep Commented Dec 5, 2022 at 11:27
  • (3) yes the lines exist only once in the file. I have got used to using 'g' global as a catch all becasue usually there are multiple replacements to be made. Not in this case though. Commented Dec 5, 2022 at 11:30
1

Assuming the XML document is well-formed and syntactically correct, you may use the XML processor xmlstarlet to toggle the values.

Ignoring the commented-out XML in the document, the following achieves this by first changing all key attributes of every keybind node from the value Right to temporary (attributes with another value will not be affected). It then changes the same attribute from s-Up to Right and finally from temporary to s-Up. The string temporary is an arbitrary string that is otherwise not used for the key attribute of the keybind node.

This effectively swaps the values from Right to s-Up and vice versa.

xmlstarlet edit \
    --var attr '//keybind/@key' \
    --update '$attr[.="Right"]'     --value temporary \
    --update '$attr[.="s-Up"]'      --value Right \
    --update '$attr[.="temporary"]' --value s-Up \
   file.xml

This would turn a document like

<?xml version="1.0"?>
<root>
  <keybind key="s-Up"/>
  <keybind key="s-Down"/>
  <keybind key="Right"/>
</root>

... into

<?xml version="1.0"?>
<root>
  <keybind key="Right"/>
  <keybind key="s-Down"/>
  <keybind key="s-Up"/>
</root>

If you insert --inplace directly after the edit word on the command line, the edit will be made in-place, modifying the original file.

1
  • that's a good solution thank you. I had not considered xmlstarlet. I now see why you wanted the full xml tag structure. I will test with that too, so that I learn from you. That is helpful. Appreciated. Commented Dec 5, 2022 at 20:10

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.