Seek Seek Revolution

The script was modified on disk while running, but it wasn't re-read. Only if I run it a second time do I get a surprise.

>

Have I misunderstood something?

That unix shell scripts read linewise is part of the trick. How the file is edited is also important; some programs use a rename(2) to move the update in place, which creates a new inode (possibly one with a different security context, which might be bad). This atomicity is usually highly desirable, but not here. Also the -i flag to sed is not portable as it was copied in various ways from perl(1) by various folks at various times. Thus the scripts shown below may need porting to other operating systems that pass for unix these days.

    $ uname;uname -r
    OpenBSD
    7.7
    $ cp orig selfedit 
    $ cat selfedit 
    #!/bin/sh
    echo start
    sed -i 's/middling/surprise/' selfedit
    echo middling
    echo end
    $ ls -i selfedit 
    9506196 selfedit
    $ sh selfedit 
    start
    middling
    end
    $ ls -i selfedit
    9506168 selfedit

"selfedit" now has a new inode number, which means the old inode went away, or will eventually go away once any programs holding onto it exit or close the file descriptor. This is how one can restore "removed but still held open files", by getting to the still-open file somehow (under /proc on a Linux system, or maybe by attaching a debugger).

If we write a program that holds a file open, we should be able to see the change, or a lack of change should the editing program instead create a new inode.

    #!/usr/bin/perl
    # sirholdsalot - hold a file open until USR1 signal
    my $file = shift // die "Usage: $0 file-to-hold-open\n";
    open my $fh, '<', $file or die "open '$file': $!\n";
    my $show_file = 0;
    $SIG{USR1} = sub { $show_file = 1 };
    do { sleep 3 } until $show_file;
    print while readline $fh;

Thus run,

    $ cp orig selfedit 
    $ perl sirholdsalot selfedit &
    [1] 75608
    $ sh selfedit 
    start
    middling
    end
    $ pgrep -lf perl
    75608 perl sirholdsalot selfedit
    20020 /usr/bin/perl /usr/local/libexec/smtpd/cuxselgre
    $ kill -USR1 75608
    $ #!/bin/sh
    echo start
    sed -i 's/middling/surprise/' selfedit
    echo middling
    echo end

    [1] + Done                 perl sirholdsalot selfedit 
    $ cat selfedit 
    #!/bin/sh
    echo start
    sed -i 's/surprise/surprise/' selfedit
    echo surprise
    echo end

the sirholdsalot script shows the original "middling" after the file has been updated by `sh selfedit`.

The next step is to edit the original file without a new inode like `sed -i` does, or may do. This can be tricky, as a naive "< file sed ... > file"† will clobber the file you're trying to read from, hence sponge(1) of moreutils having been written. Again, a lot of modern programs will use a rename(2) from a temporary file so that there's never a half-edited file seen by some other process. Assuming moreutils is installed,

    $ cp orig selfedit 
    $ perl sirholdsalot selfedit &
    [1] 86123
    $ ls -i selfedit                                         
    9506217 selfedit
    $ < selfedit sed 's/middling/surprise/' | sponge selfedit
    $ ls -i selfedit                                         
    9506217 selfedit
    $ kill -USR1 86123
    $ #!/bin/sh
    echo start
    sed -i 's/surprise/surprise/' selfedit
    echo surprise
    echo end

    [1] + Done                 perl sirholdsalot selfedit

the inode is not changed by the edit, and sirholdsalot now shows the edit to the file. What happens if we modify script without changing the inode?

    $ cat orig2
    #!/bin/sh
    echo start
    < selfedit2 sed 's/[m]iddling/surprise/' | sponge selfedit2
    echo middling
    echo end
    $ cp orig2 selfedit2
    $ sh selfedit2 
    start
    middling
    end

Nope, still not there. What if we change how the script is run?

    $ cp orig2 selfedit2 
    $ sh < selfedit2 
    start
    surprise
    end

Another important point is that file descriptor objects can be shared between processes, and thus what process A does to that object (e.g. with lseek(2)) will influence process B.

"filesys/seek-seek-revolution"

It may be helpful to use ktrace or strace or similar to see what system calls are going on whilst these various programs are running. This can be verbose, though one can usually drill down to see what is going on for the specific filenames or file descriptors involved.

† "< foo program arg > bar" is mostly the same as "program arg < foo > bar" or "program > bar arg < foo", unless program is a while loop, in which case zsh does what you want while other shells will most likely yell at you.