1

My OS: Ubuntu 18.04.5 LTS

I use tracing for troubleshooting a script. I've notice different behaviors in the tracing depending on how I set it up. I would like to understand why it behaves this way.

I normally activate tracing using set -x in my script where I need the tracing to take place. However the Prompt String PS4 ("+ " by default) seems to "duplicate" itself.

enter image description here

If I active tracing in the shebang #! line, PS4 is only printed once.

enter image description here

Similarly, if I call bash directly for running my script, and specifying -x in the command line, PS4 is also only printed once.

enter image description here

Why is set -x behaving differently in the first example ? I guess there is something fundamental about the shell which I am not getting here...

muru
  • 193,181
  • 53
  • 473
  • 722
martin_0004
  • 993
  • 5
  • 11
  • 19

1 Answers1

1

The distinction here is not where you place the -x, it's whether you specify a shell to use (either via a shebang, or by invoking the script explicitly with bash -x).

According to man bash, PS4:

   PS4    The value of this parameter is expanded  as  with  PS1  and  the
          value  is  printed  before  each command bash displays during an
          execution trace.  The first character of PS4 is replicated  mul‐
          tiple  times, as necessary, to indicate multiple levels of indi‐
          rection.  The default is ``+ ''.

however it is not forthcoming about what "levels of indirection" means in this context.

When you try to execute a text file without specifying what interpreter to use, the system call to execve fails with ENOEXEC (Exec format error). What happens next depends on the shell from which you are trying to execute it, as described here:

In particular, if the invoking shell is bash, then the script appears to get executed directly by the invoking shell: this appears to be the "indirection" mentioned in the manual:

$ strace -e trace=%process -f bash -c 'echo "Starting $0"; ./hw_no_shebang; echo "Finished."'
execve("/bin/bash", ["bash", "-c", "echo \"Starting $0\"; ./hw_no_sheb"...], 0x7ffd3dadbee8 /* 25 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7f95fe939740) = 0
Starting bash
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f95fe939a10) = 26515
strace: Process 26515 attached
[pid 26514] wait4(-1,  <unfinished ...>
[pid 26515] execve("./hw_no_shebang", ["./hw_no_shebang"], 0x555e5058a4f0 /* 25 vars */) = -1 ENOEXEC (Exec format error)
++ echo Hello
Hello
++ echo World
World
++ set +x
[pid 26515] exit_group(0)               = ?
[pid 26515] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 26515
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26515, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7fffba5272d0, WNOHANG, NULL) = -1 ECHILD (No child processes)
Finished.
exit_group(0)                           = ?
+++ exited with 0 +++

Note that the bash -c in the strace call here takes the place of your interactive shell, rather than of the bash -x that you used in your last example.

As soon as we add a shebang, ex.:

$ cat hw_shebang
#!/bin/bash

set -x
echo "Hello"
echo "World"
set +x

then (regardless of whether -x is invoked using set or otherwise), then execve succeeds and the script is executed in its own shell, without "indirection":

$ strace -e trace=%process -f bash -c 'echo "Starting $0"; ./hw_shebang; echo "Finished."'
execve("/bin/bash", ["bash", "-c", "echo \"Starting $0\"; ./hw_shebang"...], 0x7ffccc0af338 /* 25 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7f81b9ed2740) = 0
Starting bash
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f81b9ed2a10) = 26553
strace: Process 26553 attached
[pid 26552] wait4(-1,  <unfinished ...>
[pid 26553] execve("./hw_shebang", ["./hw_shebang"], 0x55879d9924f0 /* 25 vars */) = 0
[pid 26553] arch_prctl(ARCH_SET_FS, 0x7fa55de83740) = 0
+ echo Hello
Hello
+ echo World
World
+ set +x
[pid 26553] exit_group(0)               = ?
[pid 26553] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 26553
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26553, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffda2983b10, WNOHANG, NULL) = -1 ECHILD (No child processes)
Finished.
exit_group(0)                           = ?
+++ exited with 0 +++

If you'd invoked the no-shebang script from dash for example, the behavior would be different: after the initial failed execve, it would have execve'd /bin/sh to execute the script:

$ strace -e trace=%process -f dash -c 'echo "Starting $0"; ./hw_no_shebang; echo "Finished."'
execve("/bin/dash", ["dash", "-c", "echo \"Starting $0\"; ./hw_no_sheb"...], 0x7ffc283d8638 /* 25 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7fd2cc787540) = 0
Starting dash
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fd2cc787810) = 26590
strace: Process 26590 attached
[pid 26589] wait4(-1,  <unfinished ...>
[pid 26590] execve("./hw_no_shebang", ["./hw_no_shebang"], 0x557143e3c8d8 /* 25 vars */) = -1 ENOEXEC (Exec format error)
[pid 26590] execve("/bin/sh", ["/bin/sh", "./hw_no_shebang"], 0x557143e3c8d8 /* 25 vars */) = 0
[pid 26590] arch_prctl(ARCH_SET_FS, 0x7f3b9aa14540) = 0
+ echo Hello
Hello
+ echo World
World
[pid 26590] exit_group(0)               = ?
[pid 26590] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 26590
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26590, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
Finished.
exit_group(0)                           = ?
+++ exited with 0 +++
steeldriver
  • 131,985
  • 21
  • 239
  • 326