compatibility of bash parameter expansion/string manipulation - bash

I'm writing a bash script which uses parameter expansion like these a few times throughout:
${pathvariable%/*}
${pathvariable##*/}
I'll need to run this script on some boxes which may have older versions of bash installed. I know it works in bash 4.1+ but I'm wondering how portable it is?
Is this kind of construction going to fail in older versions of bash? If so, which versions?
Is there a more portable construct I should consider?

They are defined in POSIX (see here) so you should be safe.
EDIT: In case you are paranoid and don't trust in bash's compliance:
$ ./bash --posix
$ echo ${BASH_VERSION}
2.05a.0(1)-release
$ foo="foo/bar"
$ echo "${foo%/*}"
foo
$ echo "${foo##*/}"
bar
2.05a was released in ~2001.

This should not fail in any version of bash since its really a korn shell construct. As long as your sure you will be using bash (any version) your fine.

Related

Bash strips "N" character when using newline-delimiter

I am using Windows Linux Subsystem (Ubuntu). When I try to set a newline-delimiter, I lose my 'n'-characters. My simplified script;
#!/bin/sh
echo $HOME #gives /home/hennio
IFS=$'\n'
echo $HOME #gives /home/he io
IFS=$'\n\b' didnt solve the problem. I checked my shebang with $(which sh), it is correct (although using zsh).
Searching on internet didnt give any results. Can someone please tell me whats going on? It driving me nuts..
To maintain compatibility, a shell invoked as /bin/sh usually tries to emulate a POSIX shell or some variant of a Bourne shell. Neither POSIX nor Bourne support $'...'. There are two possible solutions:
Method 1: Use the shebang of a shell, like bash, that supports $'...'.
Or,
Method 2: Use a POSIX method to assigning a newline to IFS:
IFS='
'
(Hat tip: Gordon Davisson)
Documentation
From man zsh:
Zsh tries to emulate sh or ksh when it is invoked as sh or ksh
respectively
From man bash:
If bash is invoked with the name sh, it tries to mimic the
startup behavior of historical versions of sh as closely as possible,
while conforming to the POSIX standard as well.

Space between # and ! in shebang (# !/usr/bin/ksh)

I am writing a Korn shell script that involves process substitution using < <(), like this:
array=()
while IFS= read -r -d '' x;do
array+=( "$x" )
done < <(some command)
This is trying to insert into array all string returned by some command. The curious thing is that this works when my shebang looks like this:
# !/usr/bin/ksh
which is of course unusual (notice the space between # and !). On the other hand, when my shebang looks like #!/usr/bin/ksh (the right way, apparently), this script fails with the error syntax error: '< ' unexpected. Why is this? What difference does having a space in the shebang mean? Google gave me several answers saying that a space between !# and !/usr... is okay, but nothing regarding a space between ! and #.
# ! is an invalid shebang, and entirely ignored. Behavior of a script with no shebang depends on how you invoke it.
If invoked from a shell: Some shells use /bin/sh to run such scripts; others use themselves for the purpose. Presumably the shell you're interactively using when testing this (and finding the script to work only with an invalid shebang) is in the latter set, so your script is actually being run with bash, or otherwise your active interactive shell at the time.
If invoked without a shell: Most operating systems will refuse to execute such a binary.
Real David Korn ksh93 supports process substitution correctly, but some 3rd-party clones and ancient ksh implementations don't.
If you're going to use ksh, using genuine David Korn ksh93 (not mksh, pdksh, or another 3rd-party clone) is strongly preferred, and (to your immediate point) will ensure process substitution support.

vim red highlight $( )

I was writing a script when I decided to move the functions to a lib file, but when I open the lib file all the $( and the consecutive ) are red highlighted, here are some examples of the script
TAB="$(printf '\t')"
percent=$(echo "scale=2; $number/$total*100" | bc | sed -e 's/\.[[:digit:]]*//g')
if [[ -z $(grep $site/post $max_lim) ]];then
The filetype is conf but I've set it as sh syntax in .vimrc
Any idea of what is happenning?
Thank you
Edit: Thanks for the quick answers, I found that this line makes vim match the files with the extension specified behind the * with the syntax sh
au BufReadPost * set syntax=sh
I've also thought that using shebang in the libraries was not allowed, but is a nice solution
Anyway using g:is_bash in .vimrc returns an error of pattern not found
So what I would like to do is as I only write in bash, to vim recognize any file without extension as bash
The syntax file for sh actually handles several different kinds of shell syntax: bash, ksh, and plain old sh. Since your conf file isn't recognized as bash or ksh, it falls back to sh. $(...) isn't a valid construct in sh, so it is highlighted as an error.
To fix this, you can make sure "g:is_bash" is set for the file, so that the sh syntax script will know your file should be highlighted as bash code. Please edit your question to include what you added to your .vimrc to make the file use sh syntax highlighting. This will make it easier to suggest the correct way of setting "g:is_bash".
UPDATE: As Alok commented, you should be able to add the following to the file
#!/bin/bash
to let vim know the correct syntax highlighting to use as well.
In my case, I wanted to preserve #!/bin/sh as the shebang line because not every system has /bin/bash available.
Whereas the original Bourne shell may have not supported the $(...) syntax, most sh shells nowadays are POSIX-compliant, and the POSIX spec supports this syntax. For example,
On Ubuntu, /bin/sh is /bin/dash.
On MacOS, /bin/sh is /bin/bash.
On Alpine, /bin/sh is /bin/ash.
All of which satisfy the POSIX spec. Traditionally, if we'd like to write portable Shell, we should leave the shebang line as #!/bin/sh. We shouldn't change it to #!/bin/bash just for syntax highlighting if we're not going to use any Bashisms.
Okay, but what about the erroneous red highlighting? The problem is with Vim interpreting #!/bin/sh as a reference to the original Bourne shell from 1979 with no support for $(...). Maybe this is a testament to Vim's backwards compatibility, or maybe not enough people care. Here's a related GitHub issue describing the same behavior.
In any case, the best solution for me was to set let g:is_posix = 1 in my config. Interestingly, if you look through Vim's runtime files, it's equivalent to setting let g:is_kornshell = 1.
A brief interesting history on how the Bourne shell was bourne, bourne again as bash as a substitute for /bin/sh on Ubuntu, and eventually replaced in favor of dash can be found at https://askubuntu.com/a/976504.

Execution results are different when I'm executing bash script directly and through sh

I've a file new.sh
i=5
i=$[i+1]
echo $i
when I execute ./new.sh, it shows 6. But when I execute "sh new.sh", it shows
$[i+1]
as output. I just want to know why, and I need a code which will work on both.
Many Linux distributions use dash as their standard shell for scripts and therefore /bin/sh is only a symlink to /bin/dash, which is more lightweight, but lags some functionality compared with bash. Check that with:
ls -l /bin/sh
If you want to write POSIX compatible scripts, you should use foo=$n; $((n=n+1)) instead of foo=$((n++)) and foo=$((n=n+1)) instead of foo=$((++n)). The form $[] is deprecated and should be avoided.
The OS decides what type of file it is based on the first line. Make it:
#!/bin/bash
The behaviour is different because when bash is run as sh, it turns off bash-specific features and hews more closely to the POSIX shell standard.
If you want your code to work the same under both bash and sh, you will need to write your code in the sh subset of the bash syntax.

Getting bad substitution on Solaris 10 Bourne shell

I need to extract part of the string before the last opt in the string:
NDS=/opt/novell/opt/eDirectory/opt/abc
I want just the /opt/novell/opt/eDirectory/ part
I used NDSHOME=${NDS%opt*}
but I am getting bad substitution on Solaris 10, that is using the Bourne shell,
although this script works fine on Linux and AIX.
Can somebody find a solution to this??
Solaris' /bin/sh is notoriously not Posix compatible.
You could try the following workaround with sed
NDSHOME=`echo "$NDS" | sed 's/^\(.*\)\<opt\>.*$/\1/'`
Note: Normally I would use $() instead of backtics for command substitution, but I don't think those work on Solaris either.
Edit
Changed it so that opt will not match intra-word boundaries
$ echo $NDS
/opt/novell/opt/eDirectory/opt/helicopter
$ echo "$NDS" | sed 's/^\(.*\)\<opt\>.*$/\1/'
/opt/novell/opt/eDirectory/
The dirname command prints all but the last level of the path name given as an argument.
$ NDS=/opt/novell/opt/eDirectory/opt/abc
$ NDSHOME=`dirname $NDS`
$ echo $NDSHOME
/opt/novell/opt/eDirectory/opt
(Add export as needed.)
EDIT :
The above doesn't work in this case. The OP wants to remove all components of the path following the last opt, not just the last component. I'll leave it here in case it's useful to someone else.
You could use dirname in a loop, stopping when the last component (as determined by basename) is `opt':
NDSHOME="$NDS"
while [ "`basename $NDSHOME`" != opt ] ; do
NDSHOME="`dirname $NDSHOME`"
done
but that's more complex than it needs to be; sed is a better solution.
SiegeX's answer is good, but it treats opt as a string, not just as a path component. For example, it will turn
/opt/novell/opt/eDirectory/opt/helicopter
into
/opt/novell/opt/eDirectory/opt/helic
Try this:
NDSHOME=`echo "$NDS" | sed 's,\(.*\)/opt/.*$,\1,'`
(Note the use of , rather than / as a delimiter, so the / characters in the pattern don't have to be escaped.)
Solaris is definitely POSIX (and several related standards like xpg4 and susv3) compliant but it also takes compatibility with older versions quite seriously.
The side effect is that when both these features conflict in some way, the affected commands are, by default, not POSIX compliant not to break legacy scripts.
Should you want POSIX compatibility and have no legacy script issues, just set your PATH like this:
PATH=/usr/xpg6/bin:/usr/xpg4/bin:/usr/ccs/bin:$PATH
and set your login shell to /usr/xpg4/bin/sh
Alternatively, if you do not need strict compliance, you might just use /bin/ksh as your default shell.
With Solaris 10 and older, you shouldn't really use /bin/sh at all in new scripts.
For reference, here is the Solaris 10 standards manual page and the one for Solaris 11 is here.
Use the Bourne-Again shell instead.

Resources