Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I can’t imagine what BASH_ARGV0 is for. Can someone more sage supply an example of what problem it solves?


A use-case I've long wanted it for is better "--help" messages. If you want to tell the user how to invoke the program again, argv[0] is the right thing:

Given:

    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
    	printf("Usage: %s [OPTIONS]\n", argv[0]);
    	return 0;
    }
Running it as `./dir/demo --help` gives:

    Usage: ./dir/demo [OPTIONS]
Put it somewhere in $PATH, and run it as `demo --help`, and it will give:

    Usage: demo [OPTIONS]
Perfect!

But with a Bash script, argv[0] is erased, it sets $0 is set to script path passed to `bash` as an argument.

Given:

    #!/bin/bash
    echo "Usage: $0 [OPTIONS]"
Running it as `./dir/demo --help` gives:

    Usage: ./dir/demo [OPTIONS]
So good, so far, since the kernel ran "/bin/bash ./dir/demo --help". But once we get $PATH involved, $0 stops being useful, since the path passed to Bash is the resolved file path; if you put it in /usr/bin, and run it as `demo --help`, it will give:

    Usage: /usr/bin/demo [OPTIONS]
Because the call to execvpe() looks at $PATH, resolves "demo" to "/usr/bin/demo", then passes "/usr/bin/demo" to the execve() syscall, and the kernel runs "/bin/bash /usr/bin/demo --help".

In POSIX shell, $0 is a little useful for looking up the source file, but isn't so useful for knowing how the user invoked you. In Bash, if you need the source file, you're better served by ${BASH_SOURCE[0]}, rendering $0 relatively useless. And neither has a way to know how the user invoked you... until now.

It's a small problem, but one that there was no solution for.


> If you want to tell the user how to invoke the program again, argv[0] is the right thing

Some pedantry: it's actually not. The argv array is a completely arbitrary thing, passed by the caller as an array of strings and packed by the kernel into some memory at the top of the stack on entry to main(). It doesn't need to correspond to anything in particular, the use of argv[0] as the file name of the program is a side effect of the way the Bourne shell syntax works. The actual file name to be executed is a separate argument to execve().

In fact there's no portable way to know for sure exactly what file was mapped into memory by the runtime linker to start your process. And getting really into the weeds there may not even be one! It would be totally possible to write a linker that loaded and relocated an ELF file into a bunch of anonymous mappings, unmapped itself, and then jumped to the entry point leaving the poor process no way at all to know where it had come from.


Sure, argv[0] is a just string, that the caller can set when they call execve(). That doesn't have mean that it doesn't have meaning. You are correct, there is no way to know how your executable was passed as the first argument to execve(). But, argv[0] is specified to mean roughly "welcome to the world, you are argv[0]", and to tell the program what it is. Sure, you could lie to the program, and tell it that it's something it's not by passing a different string to execve(), you can even do this from a Bash script with `exec -a`.

I stand by my original statement: If you want to tell the user how to invoke the program again, argv[0] is the right thing. I didn't say that running argv[0] will necessarily actually invoke the program again, I said that it's the right thing to tell the user. If the caller set argv[0] to something else, it's because they wanted your program to believe that is its identity, so that's what it should represent its identity as to the user.


Surely the user already knows how to invoke the program, since he/she did it literally two seconds ago.

What if I invoke it from a distant path, do I want my 73 character long path to be prepended in the --help ?


Especially on something like NixOS, where /bin/bash is actually /nix/store/3508wrguwrgu3h5y9354rhfgw056y-bash-5.0/bin/bash.


Do note that was my complaint with $0, that when using $PATH it was set to that full gross path.

If /bin/foo is actually /nix/store/3508wrguwrgu3h5y9354rhfgw056y-foo-5.0/bin/foo, then when you run "foo", 0="/nix/store/3508wrguwrgu3h5y9354rhfgw056y-foo-5.0/bin/foo" and BASH_ARGV0="foo".


If you run --help you already have the path to the executable. Press the up arrow and you got it, even on nix.


argv[0] is defined to be the "program name" by ISO C. What exactly this means is obviously platform dependent, and of course the caller can ignore it altogether and put something random there, but at that point they're the ones misusing the API in a way that breaks the spec.

Note that this is ISO C, not even POSIX. So ELF, Bourne shell etc are implementation details that are out of scope on this level.


I'd love to see --help start with a few examples of the most common use cases and the parameters you would use. Include the exhaustive list of parameters after that. That would make the command line much more accessible for a significant portion of users. Or at least me. I'm always forgetting the syntax of commands I use infrequently.


According to the email, it expands to $0, though. So as far as I can see, its only use is to set $0.


Personally, I just don't bother trying to show the user their particular invocation:

    #!/bin/bash
    echo "Usage: $(basename $0) [OPTIONS]"


Minor nitpick, but wouldn't ${0##*/} work as well?


Yep! Not a nitpick at all. There's more than one way to do lots of things.

I prefer my version because it's easier for me to remember and read. I do use pattern substitution for other things, but I usually have to go to an interactive shell and create a test variable in order to remind myself whether I want # or %. I understand favoring built-in features over subshell calls for heavily used code, but I don't think this is especially critical for outputting usage info.


Yes, that's what we've been stuck doing, because the full path included in $0 is useless if the program was run via $PATH.


The “set” command lets you override the positional arguments but starting at $1. You cannot directly set $0. But you can set BASH_ARGV0, and the value of $0 changes accordingly.


“BASH_ARGV0: a new variable that expands to $0 and sets $0 on assignment.”

Changing argv[0] would make utilities like ps show a more descriptive/shorter name, eg in the case of long command paths.


I don't think changing argv[0] in the current process will have any effect in the /proc file system.

And to do what you describe, there's `exec -a NAME' already:

  $ (exec -a NOT-BASH bash -c 'echo $0; ps -p $BASHPID -f')
  NOT-BASH
  UID        PID  PPID  C STIME TTY          TIME CMD
  dualbus  18210  2549  0 19:30 pts/1    00:00:00 NOT-BASH -c echo $0; ps -p $BASHPID -f


> I don't think changing argv[0] in the current process will have any effect in the /proc file system.

Yes it does. This is a standard trick for changing the process name at runtime, several daemons do this to change the process name of child processes created by fork() that aren't separate executable. For instance, OpenSSH's sshd sets the child-process for a session to "sshd: USERNAME [priv]".

`exec -a` lets you set argv[0] through an execve() call, but many times you want to set it without exec'ing a new program.


> I don't think changing argv[0] in the current process will have any effect in the /proc file system.

Yes it does - that’s the whole point of changing it.


I would like to understand how this would work.

argv is a buffer in Bash's process memory space. This is AFAIK, not shared in any way with the kernel.

How would the kernel know that a process wrote to the memory location of argv[0] and then reflect that in /proc?

This is what I tried:

  dualbus@system76-pc:~/src/gnu/bash$ ./bash -c 'echo $BASH_VERSION; ps -p $BASHPID -f; BASH_ARGV0=NOT-BASH; echo $0; ps -p $BASHPID -f; (ps -p $BASHPID -f && : do not optimize fork)'
  5.0.0(1)-rc1
  UID        PID  PPID  C STIME TTY          TIME CMD
  dualbus  27918 20628  0 20:16 pts/5    00:00:00 ./bash -c echo $BASH_VERSION; ps -p $BASHPID -f; BASH_ARGV0=NOT-BASH; echo $0; ps -p $BASHPID -f; (ps -p $BASHPID -f && : do not optimize fork)
  NOT-BASH 
  UID        PID  PPID  C STIME TTY          TIME CMD
  dualbus  27918 20628  0 20:16 pts/5    00:00:00 ./bash -c echo $BASH_VERSION; ps -p $BASHPID -f; BASH_ARGV0=NOT-BASH; echo $0; ps -p $BASHPID -f; (ps -p $BASHPID -f && : do not optimize fork)
  UID        PID  PPID  C STIME TTY          TIME CMD
  dualbus  27921 27918  0 20:16 pts/5    00:00:00 ./bash -c echo $BASH_VERSION; ps -p $BASHPID -f; BASH_ARGV0=NOT-BASH; echo $0; ps -p $BASHPID -f; (ps -p $BASHPID -f && : do not optimize fork)


On Linux, reading /proc/<pid>/cmdline literally asks the kernel to reach into the target process's address space and fish out its argv[0]. This, erm... has some corner cases:

https://github.com/torvalds/linux/blob/v4.20/fs/proc/base.c#...

https://github.com/torvalds/linux/blob/v4.20/fs/proc/base.c#...

Changing the `ps` output in a cross platform way requires a number of platform-dependent strategies, e.g. how PostgreSQL does it:

https://github.com/postgres/postgres/blob/REL_11_1/src/backe...


Awesome, thank you! This is super useful. I stand corrected.


The argv array (and the envp array) are in a page the kernel set up when it created the process, and the kernel holds on a reference to it, and remembers the addres of those arrays in the page. The kernel doesn't need to "watch" that memory, when you read from `/proc/$pid/cmdline` or `/proc/$pid/environ`, procfs literally reads directly from $pid's memory space (remember that the kernel controls the page table, it can look in to the memory space of any process it wants to). The kernel doesn't "know" that the value changed, it just reads the current value from process' memory.


Any process can write into the argv they get from the kernel.

The kernel doesn’t need to monitor for reads - when proc reads it it’s read from the process.

It doesn’t need to be specially ‘shared’ with the kernel. The kernel can of course ready any memory it wants to from the process at any time.

I’ve implemented setting argv[0] in another language myself.


Can you show an example of how this would work with BASH_ARGV0?


Sorry I don’t know anything about how Bash is implemented, but when someone assigns to that variable Bash just needs to write that string to argv.


I tried python, bash and even C, none of them update /proc/self/comm when argv[0] is updated:

  dualbus@system76-pc:~$ cat argv0.c
  #include <stdio.h>
  #include <string.h>
  int main(int argc, char **argv) {
      FILE *fp;
      char buf[256]; // XXX :-)
      strcpy(argv[0], "XYZ");
      //puts(argv[0]);
      fp = fopen("/proc/self/comm", "r");
      fread(&buf, 1, 256, fp);
      buf[255] = '\0';
      puts(buf);
  }
  dualbus@system76-pc:~$ gcc -o argv0 argv0.c  -Wall
  dualbus@system76-pc:~$ ./argv0
  argv0

  dualbus@system76-pc:~$ python -c 'import sys; sys.argv[0] = "XYZ"; print(open("/proc/self/comm").read())'
  python

  dualbus@system76-pc:~$ ~/src/gnu/bash/bash -c 'BASH_ARGV0="XYZ"; cat /proc/$BASHPID/comm'
  bash
Furthermore, https://github.com/torvalds/linux/blob/master/Documentation/... says:

  > 3.6   /proc/<pid>/comm  & /proc/<pid>/task/<tid>/comm
  > --------------------------------------------------------
  > These files provide a method to access a tasks comm value. It also allows for
  > a task to set its own or one of its thread siblings comm value. The comm value
  > is limited in size compared to the cmdline value, so writing anything longer
  > then the kernel's TASK_COMM_LEN (currently 16 chars) will result in a truncated
  > comm value.
Which works as advertised:

  dualbus@system76-pc:~$ ~/src/gnu/bash/bash -c 'echo -n XYZ > /proc/$BASHPID/comm; ps -p $BASHPID'
    PID TTY          TIME CMD
  28797 pts/6    00:00:00 XYZ
Can you show me an example, in any language, where updating argv[0] causes ps (or /proc/self/comm) to show the updated value?

EDIT: formatting.

EDIT2: I stand corrected, see willglynn's comment.


You're looking at the wrong file. Setting argv[0] doesn't update /proc/self/comm, it updates /proc/self/cmdline.

demo.c:

    #include <unistd.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
    	strcpy(argv[0], "frob");
    	sleep(100);
    }
Terminal 1:

    $ make demo
    cc     demo.c   -o demo
    $ ./demo --greppable
Terminal 2:

    $ ps aux|grep greppable
    luke     25858  0.0  0.0   2164   752 pts/0    S+   00:32   0:00 frob o --greppable
    luke     25931  0.0  0.0   8192  2356 pts/5    S+   00:32   0:00 grep --color=auto greppable
    $ cat -v /proc/25858/cmdline
    frob^@o^@--greppable^@$


    $ ruby -e "\$0 = 'im not lying to you'; sleep"
    $ ps


>The purpose of set0 in the debugger is that users often write programs that refer to $0. For example to display a usage string or maybe they change the behavior based on how they were called. So this helps in making these programs act the same way and gives an alternative to invoking with "bash --debugger". https://lists.gnu.org/archive/html/bug-bash/2008-09/msg00054...

Yes, 2008.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: