Sub-Shell Differences Between Bash and Ksh

Sub-shell differences between bash and ksh

ksh93 works unusually hard to avoid subshells. Part of the reason is the avoidance of stdio and extensive use of sfio which allows builtins to communicate directly. Another reason is ksh can in theory have so many builtins. If built with SHOPT_CMDLIB_DIR, all of the cmdlib builtins are included and enabled by default. I can't give a comprehensive list of places where subshells are avoided, but it's typically in situations where only builtins are used, and where there are no redirects.

#!/usr/bin/env ksh

# doCompat arr
# "arr" is an indexed array name to be assigned an index corresponding to the detected shell.
# 0 = Bash, 1 = Ksh93, 2 = mksh
function doCompat {
${1:+:} return 1
if [[ ${BASH_VERSION+_} ]]; then
shopt -s lastpipe extglob
eval "${1}[0]="
else
case "${BASH_VERSINFO[*]-${!KSH_VERSION}}" in
.sh.version)
nameref v=$1
v[1]=
if builtin pids; then
function BASHPID.get { .sh.value=$(pids -f '%(pid)d'); }
elif [[ -r /proc/self/stat ]]; then
function BASHPID.get { read -r .sh.value _ </proc/self/stat; }
else
function BASHPID.get { .sh.value=$(exec sh -c 'echo $PPID'); }
fi 2>/dev/null
;;
KSH_VERSION)
nameref "_${1}=$1"
eval "_${1}[2]="
;&
*)
if [[ ! ${BASHPID+_} ]]; then
echo 'BASHPID requires Bash, ksh93, or mksh >= R41' >&2
return 1
fi
esac
fi
}

function main {
typeset -a myShell
doCompat myShell || exit 1 # stripped-down compat function.
typeset x

print -v .sh.version
x=$(print -nv BASHPID; print -nr " $$"); print -r "$x" # comsubs are free for builtins with no redirections
_=$({ print -nv BASHPID; print -r " $$"; } >&2) # but not with a redirect
_=$({ printf '%s ' "$BASHPID" $$; } >&2); echo # nor for expansions with a redirect
_=$(printf '%s ' "$BASHPID" $$ >&2); echo # but if expansions aren't redirected, they occur in the same process.
_=${ { print -nv BASHPID; print -r " $$"; } >&2; } # However, ${ ;} is always subshell-free (obviously).
( printf '%s ' "$BASHPID" $$ ); echo # Basically the same rules apply to ( )
read -r x _ <<<$(</proc/self/stat); print -r "$x $$" # These are free in {{m,}k,z}sh. Only Bash forks for this.
printf '%s ' "$BASHPID" $$ | cat # Sadly, pipes always fork. It isn't possible to precisely mimic "printf -v".
echo
} 2>&1

main "$@"

out:

Version AJM 93v- 2013-02-22
31732 31732
31735 31732
31736 31732
31732 31732
31732 31732
31732 31732
31732 31732
31738 31732

Another neat consequence of all this internal I/O handling is some buffering issues just go away. Here's a funny example of reading lines with tee and head builtins (don't try this in any other shell).

 $ ksh -s <<\EOF
integer -a x
builtin head tee
printf %s\\n {1..10} |
while head -n 1 | [[ ${ { x+=("$(tee /dev/fd/{3,4})"); } 3>&1; } ]] 4>&1; do
print -r -- "${x[@]}"
done
EOF
1
0 1
2
0 1 2
3
0 1 2 3
4
0 1 2 3 4
5
0 1 2 3 4 5
6
0 1 2 3 4 5 6
7
0 1 2 3 4 5 6 7
8
0 1 2 3 4 5 6 7 8
9
0 1 2 3 4 5 6 7 8 9
10
0 1 2 3 4 5 6 7 8 9 10

What is the difference between using -ne and != for evaluating the exit code in KornShell (ksh)?

-ne is a numeric test, != is a string test. Since you know $? is a number, it makes sense to use the numeric test.

Difference between korn and bash shell

Post from UNIX.COM

Shell features

This table below lists most features that I think would make you choose one shell over another. It is not intended to be a definitive list and does not include every single possible feature for every single possible shell. A feature is only considered to be in a shell if in the version that comes with the operating system, or if it is available as compiled directly from the standard distribution. In particular the C shell specified below is that available on SUNOS 4.*, a considerable number of vendors now ship either tcsh or their own enhanced C shell instead (they don't always make it obvious that they are shipping tcsh.

Code:

                                     sh   csh  ksh  bash tcsh zsh  rc   es
Job control N Y Y Y Y Y N N
Aliases N Y Y Y Y Y N N
Shell functions Y(1) N Y Y N Y Y Y
"Sensible" Input/Output redirection Y N Y Y N Y Y Y
Directory stack N Y Y Y Y Y F F
Command history N Y Y Y Y Y L L
Command line editing N N Y Y Y Y L L
Vi Command line editing N N Y Y Y(3) Y L L
Emacs Command line editing N N Y Y Y Y L L
Rebindable Command line editing N N N Y Y Y L L
User name look up N Y Y Y Y Y L L
Login/Logout watching N N N N Y Y F F
Filename completion N Y(1) Y Y Y Y L L
Username completion N Y(2) Y Y Y Y L L
Hostname completion N Y(2) Y Y Y Y L L
History completion N N N Y Y Y L L
Fully programmable Completion N N N N Y Y N N
Mh Mailbox completion N N N N(4) N(6) N(6) N N
Co Processes N N Y N N Y N N
Builtin artithmetic evaluation N Y Y Y Y Y N N
Can follow symbolic links invisibly N N Y Y Y Y N N
Periodic command execution N N N N Y Y N N
Custom Prompt (easily) N N Y Y Y Y Y Y
Sun Keyboard Hack N N N N N Y N N
Spelling Correction N N N N Y Y N N
Process Substitution N N N Y(2) N Y Y Y
Underlying Syntax sh csh sh sh csh sh rc rc
Freely Available N N N(5) Y Y Y Y Y
Checks Mailbox N Y Y Y Y Y F F
Tty Sanity Checking N N N N Y Y N N
Can cope with large argument lists Y N Y Y Y Y Y Y
Has non-interactive startup file N Y Y(7) Y(7) Y Y N N
Has non-login startup file N Y Y(7) Y Y Y N N
Can avoid user startup files N Y N Y N Y Y Y
Can specify startup file N N Y Y N N N N
Low level command redefinition N N N N N N N Y
Has anonymous functions N N N N N N Y Y
List Variables N Y Y N Y Y Y Y
Full signal trap handling Y N Y Y N Y Y Y
File no clobber ability N Y Y Y Y Y N F
Local variables N N Y Y N Y Y Y
Lexically scoped variables N N N N N N N Y
Exceptions N N N N N N N Y

Key to the table above.

Y Feature can be done using this shell.

N Feature is not present in the shell.

F Feature can only be done by using the shells function
mechanism.

L The readline library must be linked into the shell to enable
this Feature.

Notes to the table above

1. This feature was not in the original version, but has since become
almost standard.
2. This feature is fairly new and so is often not found on many
versions of the shell, it is gradually making its way into
standard distribution.
3. The Vi emulation of this shell is thought by many to be
incomplete.
4. This feature is not standard but unofficial patches exist to
perform this.
5. A version called 'pdksh' is freely available, but does not have
the full functionality of the AT&T version.
6. This can be done via the shells programmable completion mechanism.
7. Only by specifying a file via the ENV environment variable.

What is the difference between using `sh` and `source`?

When you call source or . (the one is an alias to the other. source cmd not POSIX - kind of bashism), you load and execute a shell script into the current shell process. So you can

  • read variables set in the sourced script,
  • use functions defined within it.
  • and even execute forks and/or subprocess if script do this.

When you call sh, you initiate a fork (sub-process or child) that runs a new session of /bin/sh (which is often a symbolic link to bash). In this case, environment variables set by the sub-script would be dropped when the sub-script terminate.

Caution: sh could be a symlink to another shell.

Practical sample

For example, if you want to change current working directory by a specific manner, you could not do

$ cat <<eof >myCd2Doc.sh
#!/bin/sh
cd /usr/share/doc
eof

$ chmod +x myCd2Doc.sh

This won't do what you expect:

$ cd /tmp
$ pwd
/tmp
$ ~/myCd2Doc.sh
$ pwd
/tmp

because current working dir is part of environment and myCd2Doc.sh would run in a subshell.

But:

$ cat >myCd2Doc.source <<eof
# Shell source file
myCd2Doc() {
cd /usr/share/doc
}
eof

$ . myCd2Doc.source
$ cd /tmp
$ pwd
/tmp
$ myCd2Doc
$ pwd
/usr/share/doc

Have a look at mycd function!! (With bash completion based on Associative Array).

Execution level $SHLVL

$ cd /tmp
printf %b '\43\41/bin/bash\necho This is level \44SHLVL.\n' >qlvl.sh

$ bash qlvl.sh
This is level 2.

$ source qlvl.sh
This is level 1.

Recursion (when a script run from itself)

$ cat <<eoqlvl2 >qlvl2.sh 
#!/bin/bash

export startLevel recursionLimit=5
echo This is level $SHLVL started:${startLevel:=$SHLVL}.
(( SHLVL < recursionLimit )) && ./qlvl2.sh
eoqlvl2
$ chmod +x qlvl2.sh

$ ./qlvl2.sh
This is level 2 started:2.
This is level 3 started:2.
This is level 4 started:2.
This is level 5 started:2.

$ source qlv2.sh
This is level 1 started:1.
This is level 2 started:1.
This is level 3 started:1.
This is level 4 started:1.
This is level 5 started:1.

A little futher

$ sed '$a ps --sid $SID fw' qlvl.sh >qlvl3.sh
$ chmod +x qlvl3.sh
$ export SID
$ read SID < <(ps ho sid $$)
$ echo $SID $$
8983 8983

( Current PID ($$ == process Id) are same identifier than SID (session ID). It's not alway true.)

$ ./qlvl3.sh 
This is level 2.
PID TTY STAT TIME COMMAND
8983 pts/10 Ss 0:00 /bin/bash
10266 pts/10 S+ 0:00 \_ /bin/bash ./qlvl3.sh
10267 pts/10 R+ 0:00 \_ ps --sid 8983 fw

$ . qlvl3.sh
This is level 1.
PID TTY STAT TIME COMMAND
8983 pts/10 Ss 0:00 /bin/bash
10428 pts/10 R+ 0:00 \_ ps --sid 8983 fw

Dot . is an alias of source. So the only difference between two command are slash replaced by space.

And a final test:

$ printf %b '\43\41/bin/bash\necho Ending this.\nsle' \
'ep 1;exit 0\n' >finalTest.sh

$ bash finalTest.sh
Ending this.

$ source finalTest.sh
Ending this.

... You may notice a different behaviour between the two syntaxes. ;-)



Related Topics



Leave a reply



Submit