2

I'm trying to write a bash script that can edit itself. The line of code I'm trying to use is:

sed "s/$STRING_TO_REPLACE/$NEW_STRING/" < $0 > $0

But after running the script I end up with an empty file. Any suggestions?

I am creating a script that has two variables that contain default values. The only problem is, these default values will be different for each of the many stations where it will be used. I would rather none of the people running these stations had to open the script and edit the defaults themselves, so I want to include the ability to change the defaults in the script. One of the requirements I have is that the script must be self contained; no other files are to accompany the script and there are to be no environment variables. This would require the script to be able to edit itself.

Jake Greene
  • 5,539
  • 2
  • 22
  • 26
  • 1
    I'm curious, what for, experiment in polymorphic scripting, or do you actually think this will be useful. – C. Ross Jul 02 '10 at 19:15
  • this could help: http://stackoverflow.com/questions/3055005/bash-is-it-ok-to-use-same-input-file-as-output-of-a-piped-command – Amro Jul 02 '10 at 19:17
  • 1
    consider haveing 2 scripts. 1 is the controller which creates the other on the fly then executes it. – DwB Sep 18 '13 at 16:37

7 Answers7

2

If you want to do an in-place substitution with sed, use the -i switch.

I wouldn't recommend re-writing the currently executing script on the fly. I'm not sure whether it's possible or not, but if it is, it would certainly be interesting (read: difficult) code to maintain :-)

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • I understand that it's ugly. I'll have to think of another way to do this. – Jake Greene Jul 05 '10 at 20:14
  • He doesn't need to edit a script in mid-execution. He can simply force the script to exit when a certain condition is met, such as true or false boolean, then have a slave node (external script) edit the master node (main script) and change the proper lines as he/she desires. – Yokai Dec 14 '16 at 07:07
  • FWIW the `-i` switch seems to move the original file and write a new one in its place, so Bash won't pick up the change (at least not in the experiment I just ran). To actually edit a running shell script and have the interpreter respond accordingly, it may be necessary to read the entire script into a variable or temporary file, process it with `sed`, and then send the output of `sed` to `$0` using shell redirection rather than the `-i` option. (This might be system-dependent.) – David Z Oct 30 '19 at 18:39
2

You're trying to read and write to the same stream, this is a bad programming practice and usually results in errors.

This is what you should do:

  1. Write to a temp file
  2. Delete the original file
  3. Rename temp to the original file name

    sed "s/$STRING_TO_REPLACE/$NEW_STRING/" < $0 > temp

    rm $0

    mv temp $1

I haven't done bash scripting in while, so verify that I have the right commands.

ChickSentMeHighE
  • 1,706
  • 6
  • 21
  • 30
  • The rm is not needed, it should be "mv temp $0", and I have to perform chmod at the end to make the new file executable. Other than that it works :) I wasn't aware that I could delete the script while it was running. – Jake Greene Jul 02 '10 at 19:24
1

But after running the script I end up with an empty file. Any suggestions?


a somewhat belated response... one possible solution is that your script could contain modifiable settings after the script's exit command.

to avoid accidental changes to anything but these settings, it would be a good idea to use an appropriate prefix like so:

@SETTINGS
@setting-homepage=http://www.goatse.cx
@setting-dns=4.4.2.2

below is an example that modifies two variables: one that specifies the default browser homepage (default: www.goatse.cx, new: www.google.com) and another to change DNS (default: 4.4.2.2, new: 8.8.8.8).



word of warning

sed is used to modify the contents of the line number containing the desired setting. if there are errors here, it will spaz out and overwrite every line in the script with sed-related madness. one solution could be to have the script write another script that then works to modify the original script. another way could be to include a conditional statement that only executes the sed substitution after confirming that the match exists. or perhaps more simply, matching the desired setting string instead of just the line number.

EDIT: it seems that attempting to perform string match with sed results in some strange script autophagy. i think you need to specify line numbers that are below the exit command.



#!/bin/bash

# find settings and extract their values
settingDefaultHomepage=$(grep ^@setting-homepage $0 |  sed 's/@setting-homepage=*//')
settingDefaultDNS=$(grep ^@setting-dns $0 |  sed 's/@setting-dns=*//')


echo "Default browser homepage = $settingDefaultHomepage"
echo "Default DNS = $settingDefaultDNS"


# find line number of setting, to be used when modifying the value
settingLineHomepage=$(awk '/^@setting-homepage/ {print FNR}' $0)
settingLineDNS=$(awk '/^@setting-dns/ {print FNR}' $0)

echo "Line of setting for browser homepage = $settingLineHomepage"
echo "Line of setting for DNS = $settingLineDNS"



# modify the settings using line number above
sed -i $settingLineHomepage's/.*/@setting-homepage=www.google.com/' $0
sed -i $settingLineDNS's/.*/@setting-dns=8.8.8.8/' $0



exit


@SETTINGS
@setting-homepage=www.goatse.cx
@setting-dns=4.4.2.2



faustus
  • 313
  • 2
  • 10
1

Firstly, this is a very bad idea and I can't imagine any use case for it that can't be covered by something not this.

The reason this is happening is because when you do >file, it opens the file in read mode and truncates it to 0 length, and the input has nothing to read. You need to use a temp file. You can see more info here.

Daenyth
  • 35,856
  • 13
  • 85
  • 124
  • 1
    He can simply force the script to exit when a certain condition is met, such as true or false boolean, then have a slave node (external script) edit the master node (main script) and change the proper lines as he/she desires. Or he can even use a config file to save on-the-fly data changes, have the script exit, then re-run with the new config changes. I assume he may be testing small simplex A.I. theories. (i.e. Self-correcting code). Bash is perfectly fine for testing small simplex theories such as this. – Yokai Dec 14 '16 at 07:12
0

EDIT: As DwB commented above, separate yours into two codes: One core script that does what you want, and the other, which modifies, executes, and maybe terminates the first. This is the first thing you should do.

Also remember that you should not edit a running shell script. Read my answer to the question Edit shell script while it's running.

teika kazura
  • 1,348
  • 1
  • 16
  • 21
  • He doesn't need to edit a script in mid-execution. He can simply force the script to exit when a certain condition is met, such as true or false boolean, then have a slave node (external script) edit the master node (main script) and change the proper lines as he/she desires. I assume he may be testing small simplex A.I. theories. (i.e. Self-correcting code). – Yokai Dec 14 '16 at 07:10
0

What I'm doing, and I also recommend, is having a configuration file ($HOME/.myscript.cfg) and having your script detect its presence (and version) and create/update it accordingly. This way the first run can ask questions and subsequent runs can do their job without encumbering the operator with repetitive questions.

Liz Av
  • 2,864
  • 1
  • 25
  • 35
-1

Perl handles in place edits. Try this:

perl -npi -e "s/$STRING_TO_REPLACE/$NEW_STRING/g" < $0
Robert Wohlfarth
  • 1,761
  • 11
  • 10
  • This won't work because you're giving the file on STDIN so there's no file to in-place modify. Remove the `<` for it to do what you're trying. – Daenyth Jul 02 '10 at 20:03