Bash script that edits itself
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 开发者_Go百科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.
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 :-)
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:
- Write to a temp file
- Delete the original file
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.
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.
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
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.
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.
Perl handles in place edits. Try this:
perl -npi -e "s/$STRING_TO_REPLACE/$NEW_STRING/g" < $0
精彩评论