In Unix, most editors work by creating a new temporary file containing the edited contents. When the edited file is saved, the original file is deleted and the temporary file renamed to the original name. (There are, of course, various safeguards to prevent dataloss.) This is, for example, the style used by sed
or perl
when invoked with the -i
("in-place") flag, which is not really "in-place" at all. It should have been called "new place with old name".
This works well because unix assures (at least for local filesystems) that an opened file continues to exist until it is closed, even if it is "deleted" and a new file with the same name is created. (It's not coincidental that the unix system call to "delete" a file is actually called "unlink".) So, generally speaking, if a shell interpreter has some source file open, and you "edit" the file in the manner described above, the shell won't even see the changes since it still has the original file open.
[Note: as with all standards-based comments, the above is subject to multiple interpretations and there are various corner-cases, such as NFS. Pedants are welcome to fill the comments with exceptions.]
It is, of course, possible to modify files directly; it's just not very convenient for editing purposes, because while you can overwrite data in a file, you cannot delete or insert without shifting all following data, which would imply quite a lot of rewriting. Furthermore, while you were doing that shifting, the contents of the file would be unpredictable and processes which had the file open would suffer. In order to get away with this (as with database systems, for example), you need a sophisticated set of modification protocols and distributed locks; stuff which is well beyond the scope of a typical file editing utility.
So, if you want to edit a file while its being processed by a shell, you have two options:
You can append to the file. This should always work.
You can overwrite the file with new contents of exactly the same length. This may or may not work, depending on whether the shell has already read that part of the file or not. Since most file I/O involves read buffers, and since all the shells I know read an entire compound command before executing it, it is pretty unlikely that you can get away with this. It certainly wouldn't be reliable.
I don't know of any wording in the Posix standard which actually requires the possibility of appending to a script file while the file is being executed, so it might not work with every Posix compliant shell, much less with the current offering of almost- and sometimes-posix-compliant shells. So YMMV. But as far as I know, it does work reliably with bash.
As evidence, here's a "loop-free" implementation of the infamous 99 bottles of beer program in bash, which uses dd
to overwrite and append (the overwriting is presumably safe because it substitutes the currently executing line, which is always the last line of the file, with a comment of exactly the same length; I did that so that the end result can be executed without the self-modifying behaviour.)
#!/bin/bash
if [[ $1 == reset ]]; then
printf "%s\n%-16s#\n" '####' 'next ${1:-99}' |
dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^#### $0 | cut -f1 -d:) bs=1 2>/dev/null
exit
fi
step() {
s=s
one=one
case $beer in
2) beer=1; unset s;;
1) beer="No more"; one=it;;
"No more") beer=99; return 1;;
*) ((--beer));;
esac
}
next() {
step ${beer:=$(($1+1))}
refrain |
dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^next\ $0 | cut -f1 -d:) bs=1 conv=notrunc 2>/dev/null
}
refrain() {
printf "%-17s\n" "# $beer bottles"
echo echo ${beer:-No more} bottle$s of beer on the wall, ${beer:-No more} bottle$s of beer.
if step; then
echo echo Take $one down, pass it around, $beer bottle$s of beer on the wall.
echo echo
echo next abcdefghijkl
else
echo echo Go to the store, buy some more, $beer bottle$s of beer on the wall.
fi
}
####
next ${1:-99} #
That is because a starting X session never reads your ~/.bashrc
and ~/.profile
. Usually, the desktop manager is started as root or its own user from an init script. The resulting process usually has the environment of the init process when it hits the desktop manager starting script. (I will not talk about less established init implementations at this point.)
After login it spawns a child process, which inherits just that environment. The child process drops privileges to your uid and runs /etc/X11/Xsession
, which usually simply runs all the scripts in /etc/X11/Xsession.d
. These scripts usually either set environment variables, or, specifically modify the invocation of the desktop environment.
In that directory you may find a file like 40x11-common_xsessionrc
on Debian systems, which sources your ~/.xsessionrc
. The last file in /etc/X11/Xsession.d
will start your desktop environment. All your GUI processes will be spawned from this process, thus inherit the environment set up through /etc/X11/Xsession.d
and consequently your ~/.xsessionrc
.
~/.xsessionrc
is a script file, which may source ~/.bashrc
but that's really bad style, because then your DE sources it, the terminal process inherits that environment and then bash sources ~/.bashrc
again, which may have unwanted side effects.
However, it is totally acceptible to have a ~/.myenvironmentvariables
, which contains stricly (environment) variable definitions and is sourced by ~/.bashrc
and ~/.xsessionrc
. If you do that, all your processes will inherit the variables specified in ~/.myenvironmentvariables
, especially the shell scripts you double click in Nautilus.
Best Answer
With
bash
4.1 and above, you can do(also works when
bash
is invoked assh
).Basically, we're telling
bash
to output thextrace
output on file descriptor 7 instead of the default of 2, and redirect that file descriptor to/dev/null
. The fd number is arbitrary. Use a fd above 2 that is not otherwise used in your script. If the shell you're entering this command in isbash
oryash
, you can even use a number above 9 (though you may run into problems if the file descriptor is used internally by the shell).If the shell you're calling that
bash
script from iszsh
, you can also do:for the variable to be automatically assigned the first free fd above 9.
For older versions of
bash
, another option, if thextrace
is turned on withset -x
(as opposed to#! /bin/bash -x
orset -o xtrace
) would be to redefineset
as an exported function that does nothing when passed-x
(though that would break the script if it (or any otherbash
script it invokes) usedset
to set the positional parameters).Like:
Another option is to add a DEBUG trap in a
$BASH_ENV
file that doesset +x
before every command.That won't work when
set -x
is done in a sub-shell though.As @ilkkachu said, provided you have write permission to any folder on the filesystem, you should at least be able to make a copy of the script and edit it.
If there's nowhere you can write a copy of the script, or if it's not convenient to make and edit a new copy every time there's an update to the original script, you may still be able to do:
That (and the copy approach) may not work properly if the script does anything fancy with
$0
or special variables like$BASH_SOURCE
(such as looking for files that are relative to the location of the script itself), so you may need to do some more editing like replace$0
with the path of the script...