mainThe GNU Bourne-Again SHell - Support: sr #108980, Unexpected behaviour - sourcing...

 
 

sr #108980: Unexpected behaviour - sourcing file that contains "exec something-non-existent" closes the shell

Submitter:  None
Submitted:  Tue 23 Feb 2016 11:36:36 AM UTC
   
 
Category:  None Priority:  5 - Normal
Severity:  3 - Normal Status:  Wont Do
Privacy:  Public Assigned to:  None
Originator Email:  -email is unavailable- Open/Closed:  Open
Operating System:  GNU/Linux
* Mandatory Fields

Add a New Comment Rich Markup
   

Wed 24 Feb 2016 05:22:54 PM UTC, comment #1: 

The shell is not currently interactive when reading a file argument to the source builtin (interactive == 0), though the shell itself is interactive (interactive_shell == 1).  There
are other circumstances when an interactive shell is not
currently interactive (command substitution, executing traps,
running startup files, etc.)

I have not completely decided what to do about this, but I am not inclined right now to change the current behavior.

Chet Ramey <chet>
Group administrator
Tue 23 Feb 2016 11:36:36 AM UTC, original submission:  

Good local!

Some time ago the poster of this question on StackOverflow had found a strange behaviour.

exec1.sh:
   
    #/bin/bash
    exec "non-existing-file"

non-existing-file command does not exist.
Now run:
  
    source exec1.sh

It closes the shell, as if it were non-interactive shell or existing command.

(Why)
(in the `exec.def` code, we see:

    if (subshell_environment || (interactive == 0 && no_exit_on_failed_exec == 0))
      exit_shell (exit_value);

so, exiting shell would be done
a) on non-interactive shell with `false` "no_exit_on_failed_exec"
b) when `exec` is being run in subshell

For the interactive shell, if the command exist, the shell is replaced by the command invoked).

Now about source:

> When a script is run using `source' it runs within the existing shell,
> any variables created or modified by the script will remain available
> after the script completes.

http://ss64.com/bash/source.html

Will check it:
`source inter.sh`

    *inter.sh:*
    # Check if the shell is interactive
    # based on http://www.tldp.org/LDP/abs/html/intandnonint.html

    #!/bin/bash
    echo "First method:"
    echo $-
    echo "Second method:"
    if [[ -z $PS1 ]] # no prompt?
    then
      # non-interactive
      echo "non-interactive"
    else
      # interactive
      echo "interactive"
    fi

The output:

> First method:
> himBH
> Second method:
> interactive


So, one may convince, `source`, does not fork, and uses current interactive shell.

The situation seems alike when just running `exec non-existent-file`. *So, why does it terminate while using `source`*?

I've compiled `bash-4.2` with `gcc` `-g` flag.

This is `exec1.sh`:

    #!/bin/bash
    exec "non-existing-file"

I've run it in `gdb` debugger.

    chmod +x exec1.sh
    gdb bash
    r
    ./exec1.sh
    ./exec1.sh: line 2: exec: non-existing-file: not found

    source exec1.sh
    bash: exec: non-existing-file: not found
    [Inferior 1 (process 13773) exited with code 0177]

What is 0177? It is not in standard bash exit codes. Though, some web search help us suggest, it is exit code for exec on "File not found".

For trace see attachment.

These are the last commands that being executed before exit:

    (gdb)
    bash: exec: non-existing-file: не найден
    163   exit_value = EX_NOTFOUND; /* As per Posix.2, 3.14.6 */
    (gdb)
    165       goto failed_exec;
    (gdb)
    235   FREE (command);
    (gdb)
    237   if (subshell_environment || (interactive == 0 && no_exit_on_failed_exec == 0))
    (gdb)
    238     exit_shell (exit_value);
    (gdb)
    [Inferior 1 (process 4034) exited with code 0177]

Printing the variables:

    (gdb) p subshell_environment
    $1 = 0
    (gdb) p interactive
    $2 = 0
    (gdb) p no_exit_on_failed_exec
    $3 = 0

It turns out, the shell is non-interactive (`interactive=0`) while sourceing the built-in `exec`. This is the reason for such behaviour.
It conradicts the documented behaviour, so, one may say, you've discovered a bug.

Where does `interactive` is set? (using `gdb's "watch" feature`)

    source exec1.sh

    Hardware watchpoint 2: interactive

    Old value = 1
    New value = 0
    _evalfile (filename=0xa017a8 "exec1.sh", flags=14) at evalfile.c:226
    226   return_catch_flag++;

And it is here: (`evalfile.c`)

    223  if (flags & FEVAL_NONINT)
    224    interactive = 0;

As one could obtain studying the call stack and stepping through code in gdb, sourcing the file really is performed this way: the whole file contents are combined as one big string in `evalfile.c`, the it is parsed to find separate strings and execute them with `evalstring.c`.
`evalfile`

(gdb) p flags
$1 = 14
#define FEVAL_NONINT 0x008
-> 14=1110 & 8=1000 -> 1000 -> interactive = 0 (non-interactive)
The problem is the 4th bit from the right in flags.

#1  0x0000000000485dcf in source_file (filename=0x9fb8c8 "exec1.sh", sflags=0)
    at evalfile.c:344
344   rval = _evalfile (filename, flags);

Here is the relevant code of source_file:

338  flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT;
339  if (sflags)
340    flags |= FEVAL_NOPUSHARGS;
341  /* POSIX shells exit if non-interactive and file error. */
342  if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0)
343    flags |= FEVAL_LONGJMP;
344  rval = _evalfile (filename, flags);

sflags=0, interactive_shell=1 here, so
flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT;

#define FEVAL_BUILTIN 0x002
#define FEVAL_UNWINDPROT 0x004
#define FEVAL_NONINT 0x008

-> flags 1110 =14.

Result: interactive shell converts
flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT;

At last:
Why doing `source inter.sh`  gives us `i` flag (interactive)?

using `watch` in `gdb` again:
`source inter.sh`
Hardware watchpoint 2: interactive

Old value = 1
New value = 0
_evalfile (filename=0xa014c8 "inter.sh", flags=14)
at evalfile.c:226
    224    interactive = 0;

> First method:
> himBH
> Second method:
> interactive


The bash variables `$PS1` and `$-` and `interactive=0` in internals seem to be not in line with each other.

So, as I understand, it is a bug: `source` executes commands in the current shell, but sets flag `FEVAL_NONINT 0x008` - non-interactive
(bug here):
flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT;

Anonymous

 

(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

Attach Files:
   
   
Comment:
   

Attached Files
file #36438:  trace.txt added by None (2KiB - text/plain)

 

Depends on the following items: None found

Items that depend on this one: None found

 

Carbon-Copy List
  • -email is unavailable- added by chet (Posted a comment)
  • -email is unavailable- added by None (Submitted the item)
  •  

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only logged-in users can vote.

     

    Follow 3 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2023-01-17 chet StatusPostponed Wont Do
    2016-02-24 chet StatusNone Postponed
    2016-02-23 None Attached File- Added trace.txt, #36438

    Back to the top

    Powered by Savane 3.13-758e.
    Corresponding source code