LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Bash; terminate sourced script without closing its shell (http://www.linuxquestions.org/questions/programming-9/bash%3B-terminate-sourced-script-without-closing-its-shell-4175416148/)

porphyry5 07-11-2012 12:05 PM

Bash; terminate sourced script without closing its shell
 
What command within a sourced script will stop execution and return to it's shell's command prompt?

pan64 07-11-2012 12:19 PM

basically there is no way to do this, but there are some workarounds:
inside a loop you can use break (so implement a "virtual" endless loop and you will break it for sure)
you can move all the sourced script into a function and you can return from the function any time.

porphyry5 07-11-2012 12:50 PM

Quote:

Originally Posted by pan64 (Post 4725145)
basically there is no way to do this, but there are some workarounds:
inside a loop you can use break (so implement a "virtual" endless loop and you will break it for sure)
you can move all the sourced script into a function and you can return from the function any time.

Thank you, that's what I have been doing, but always wondered if there was a kosher method.

David the H. 07-11-2012 01:40 PM

When you source a file, you're essentially merging it with the parent, so that they become one single script. There isn't any boundary between them.

Sourcing, especially into an interactive shell, really is best suited for importing static data, IMO, such as variable or function definitions, or at most short conditional blocks that alter environment settings. Using it to actually run commands has always seemed rather risky to me. Is there any reason you can't just make the external file an executable script of its own, and call/control it like any other command?

porphyry5 07-11-2012 01:57 PM

Quote:

Originally Posted by David the H. (Post 4725233)
Is there any reason you can't just make the external file an executable script of its own, and call/control it like any other command?

None, except I use source to run scripts that I'm developing, because its variable values are still available after the script exits, which is not true of direct execution, and I know of no other way of achieving that except with "set", which buries one in its output. Its much more convenient just to do "echo $varname" for debugging purposes.

David the H. 07-11-2012 02:03 PM

Set the variables in the main shell first, and export them. Then they'll be visible to your script too.

Frankly, I just set the variables in the script itself, otherwise you have to keep track of where the values are coming from, and that can get confusing when writing a long script. I also always just have two or more consoles open at once; one for writing and one for testing.

PTrenholme 07-11-2012 02:04 PM

You should think of a sourced file as though it was an "include" in a compiled program. That is, since bash in an interpreted language, no a compiled one, the source command just pushes the current input down the input stack and adds whatever you've "sourced," to the top of the input stack. It will happily read from that "source" until the EOF is hit, after which the prior "source" is popped from the stack and input from it is resumed.

I almost never use sourced files for anything but function definitions, which I keep in /usr/share/bash. Here's a typical use:
Code:

$ cat /etc/rc.d/rc.local
#!/bin/sh
# Load the local function definitions
. /usr/share/bash/include
include define_colors sucmd connect_using_nmcli_to vg_activate mount_noauto mount_cifs mount_iso
echo
echo -----------------------------------------------------
echo Starting $0

# Connect to the Eathernet network if it's available
connect_using_nmcli_to System
#connect_using_wicd

# Activate and mount any available, unmounted, volume groups with mount points in /etc/fstab
vg_activate

# Mount any "noauto" or "nofail" devices listed in /etc/fstab that exist and are not already mounted
mount_noauto

# Mount any "noauto" cifs devices
mount_cifs

# Mount up to max_loop ISO files located in /ISO under /mnt/ISO/
mount_iso
echo -----------------------------------------------------
echo

and my definition of my include function and an example of two functiona:
Code:

$ cd /usr/share/bash/
$ ls
bash_alias.sh          define_colors        mount_cifs    Ping        wait_for_connection
connect_using_nmcli_to  define_console_codes  mount_iso    sucmd
connect_using_wicd      include              mount_noauto  vg_activate
$ cat include
#!/bin/bash
#
# Note: This bash script is symlinked as /usr/local/bin/include
#
# Define the function to include all the programs listed in "$*" from /usr/share/bash
#
# Usage: include function1 {function2 ...}
#
# Note: The function assumes that any function to be included is an executable script,
#      and searched $PAQTH for any match before looking in /usr/share/bash
#
# Define the include function if it's not already defined
if [ "$(type -t include)" != "function" ]
then
  include ()
  {
    local f abort loc
    abort=0
    for f in $*
    do
      loc=$(type -P ${f})
      if [ -z "${loc}" ]
      then
        if ! [ -e "/usr/share/bash/${f}" ]
        then
          echo Error: The bash include file \""/usr/share/bash/${f}"\" could not be found. > /dev/stderr
          $((++abort))
        else
          loc="/usr/share/bash/${f}"
        fi
      if [ -x "${loc}" ]
      then
        . "${loc}"
      else
        echo Error: The bash include file \""${loc}"\" is not executable. > /dev/stderr
        $((++abort))
      fi
    fi
    done
    [ ${abort} -gt 0 ] && echo "${abort} files could not be found in /usr/share/bash." > /dev/stderr
    return ${abort}
  }
fi
# Sanity checks:
#
# Do we have any arguments?
#
if [ $# -gt 0 ]
then
  include "$*"
else
  cat <<EOF >/dev/stderr
include: Usage "include file {file . . .}

where "file . . ." are executable files in /usr/share/bash to be sourced in the script.

EOF
fi
$ cat sucmd
#############################################################################
#
# Run a command as "root" and return its returned value
#
#############################################################################
sucmd() {
  local error_code
  # Make sure the color codes are defined
  [ -z "${csi}" ] && define_colors
  if [ $(id -u) -eq 0 ]
  then
    sucmd=
  else
    sucmd="sudo"
  fi
  echo -n ${bold_blue}
  ${sucmd} $*
  error_code=$?
  echo -n ${reset}
  return ${error_code}
}
$ cat define_colors
####################################################################################
#
# Console code definitions (foreground colors only)
#
# See man console_codes for a full description
#
####################################################################################
define_colors()
{
  csi=$'\x1B['
  reset="${csi}0m"
  bold="${csi}1m"
  black="${csi}30m"
  red="${csi}31m"
  green="${csi}32m"
  brown="${csi}33m"
  blue="${csi}34m"
  magenta="${csi}35m"
  cyan="${csi}36m"
  white="${csi}37m"
  bold_red="${bold}${red}"
  bold_green="${bold}${bold_green}"
  bold_brown="${bold}${brown}"
  bold_blue="${bold}${blue}"
  bold_magenta="${bold}${magenta}"
  bold_cyan="${bold}${cyan}"
  underscore_on="${csi}38m"
  underscore_off="${csi}39m"
}


porphyry5 07-11-2012 02:46 PM

Quote:

Originally Posted by David the H. (Post 4725271)
Set the variables in the main shell first, and export them. Then they'll be visible to your script too.

Frankly, I just set the variables in the script itself, otherwise you have to keep track of where the values are coming from, and that can get confusing when writing a long script. I also always just have two or more consoles open at once; one for writing and one for testing.

Not sure if we are talking at cross purposes here. Its the variables within the script that is executing that I'm interested in. If it terminates exceptionally, their final values are still available if source was used, and usually one can debug the situation with no other reference. I believe this is so because source still holds the shell in which the script executed open, and it is a great convenience.

porphyry5 07-11-2012 03:08 PM

Quote:

Originally Posted by PTrenholme (Post 4725273)
You should think of a sourced file as though it was an "include" in a compiled program. That is, since bash in an interpreted language, no a compiled one, the source command just pushes the current input down the input stack and adds whatever you've "sourced," to the top of the input stack. It will happily read from that "source" until the EOF is hit, after which the prior "source" is popped from the stack and input from it is resumed.

I'm not understanding the objection to my using source the way I do. If it is a security issue, it doesn't exist for me, I am the sole user of my home computers, and my scripts are only ever run as user. My only affection for "source" is that it makes debugging so much more convenient than anything else I know. If there is an alternative method of testing scripts as convenient as "source", I'm quite willing to use it, but I don't know of any such. All I want is for the script's final variable values to be still available when the script terminates.

Reuti 07-12-2012 10:22 AM

It depends on your sourced script. Donít use exit anywhere, but set a variable in case you want to exit and use it in upcoming if-then-else to avoid any further execution. Somehow I remember doing this for Pascal functions to get to the end.

David the H. 07-12-2012 11:18 AM

Quote:

Originally Posted by porphyry5 (Post 4725301)
Not sure if we are talking at cross purposes here. Its the variables within the script that is executing that I'm interested in. If it terminates exceptionally, their final values are still available if source was used, and usually one can debug the situation with no other reference. I believe this is so because source still holds the shell in which the script executed open, and it is a great convenience.

Yeah, my bad. Chock it up to drowsiness; it was my final post before crashing last night, and I was thinking in terms of input rather than output.

I agree then that it does offer some convenience, but as another option you might consider outputting them into a textfile instead. A quick "declare > file" at the end of the script, or a similar debugging function, should do it. Then it's just a quick less or grep to view them.

PTrenholme 07-12-2012 11:45 AM

Quote:

Originally Posted by porphyry5 (Post 4725320)
I'm not understanding the objection to my using source the way I do. If it is a security issue, it doesn't exist for me, I am the sole user of my home computers, and my scripts are only ever run as user. My only affection for "source" is that it makes debugging so much more convenient than anything else I know. If there is an alternative method of testing scripts as convenient as "source", I'm quite willing to use it, but I don't know of any such. All I want is for the script's final variable values to be still available when the script terminates.

That's a good reason to use shell functions. Inside the function you can declare any variables you don't want to return as local, and any other variables you use will be available to the rest of your program. All this without needing to export the variables or, in fact, anything else.

The advantage of the "function" method is that, once you get the function debugged, you can pop it into a function library and either source it or (my preference) use something like my include function above to source in whatever you want from your library.

porphyry5 07-13-2012 11:31 AM

Quote:

Originally Posted by David the H. (Post 4726105)
Yeah, my bad. Chock it up to drowsiness; it was my final post before crashing last night, and I was thinking in terms of input rather than output.

I agree then that it does offer some convenience, but as another option you might consider outputting them into a textfile instead. A quick "declare > file" at the end of the script, or a similar debugging function, should do it. Then it's just a quick less or grep to view them.

Thank you, that works very well, though better with grep than less.

porphyry5 07-13-2012 12:54 PM

Quote:

Originally Posted by PTrenholme (Post 4726122)
That's a good reason to use shell functions. Inside the function you can declare any variables you don't want to return as local, and any other variables you use will be available to the rest of your program. All this without needing to export the variables or, in fact, anything else.

The advantage of the "function" method is that, once you get the function debugged, you can pop it into a function library and either source it or (my preference) use something like my include function above to source in whatever you want from your library.

I can write the entire script as a function, paste the text of it into xterm and run the function name and all is well. But the last script I wrote exceeded 170 lines, which is rather unwieldy to select and paste, and I can't find any command other than "source" or its alias "." that will load it into the shell. I tried running it as an executable file containing just the function itself, and also preceded with a shebang line as usual, but they of course ran in their own subshells, so the function was not available back in the main shell when I tried to use it.
Code:

~ $ cat ~/Scripts/junk.sh
junk(){
a=6
c=7
b=8
}
~ $ ~/Scripts/junk.sh
~ $ junk
bash: junk: command not found
# put shebang line back in junk.sh and tried again
~ $ ~/Scripts/junk.sh
~ $ junk
bash: junk: command not found
~ $

I could use xsel and xdotool to physically paste the content of junk.sh into xterm, but why do something so long-winded when I can just use "."

Unless they're really trivial, in which case I do them as functions in ~/.bashrc, I develop and test all my scripts as junk.sh, and run them via
Code:

alias j='source /home/g/Scripts/junk.sh'
Hard to make it more convenient than that, so absent some compelling reason to abandon "source" ...

porphyry5 07-13-2012 01:09 PM

Quote:

Originally Posted by Reuti (Post 4726062)
It depends on your sourced script. Donít use exit anywhere, but set a variable in case you want to exit and use it in upcoming if-then-else to avoid any further execution. Somehow I remember doing this for Pascal functions to get to the end.

I use a variable to control subsequent flow when inside an inner loop and arrive at a condition that requires breaking the outer loop as well, or it could be extended to a chain of them if need be. But to break from just a single loop its easier to use break by itself.


All times are GMT -5. The time now is 12:52 AM.