0

I have a bunch of shell scripts which incorrectly assume /bin/sh to be equivalent to /bin/bash. E.g., they have the #!/bin/sh shebang, but use the source command instead of . (dot).

I run Ubuntu 16, where /bin/sh links to dash, and thus bash-isms are not supported.

I need to run the scripts periodically. Also, from time to time I will need to update them from the original author, who is not into fixing this particular bug. I would like to avoid fixing all these files (there are a bunch of them, they are not mine, and I'll loose all the changes after update). Also, I would like to avoid making global changes to system, since it might potentially break other scripts.

Is there a way to somehow create a (temporary or not) environment with /bin/sh pointing to bash, to be used for these scripts, while not touching the global system /bin/sh?

9
  • 1
    Do not replace /bin/sh! You have no way to know which other programs, scripts and so on rely on /bin/sh (a lot!), and this may seriously break your system. Instead, fix your shell scripts! Commented Sep 12, 2018 at 0:06
  • Thanks. I was hoping there was a way to create a separate environment and avoid global system changes. I've clarified my question a little. Commented Sep 12, 2018 at 7:28
  • @AlexChe : So you have a set of programs with errors in it, and solve the problem by temporarily introducing an even bigger error in your system? If the programs have an incorrect #! line, the maintainers simply have to fix them. Commented Sep 12, 2018 at 7:46
  • @user1934428 , I have a set of programs which maintainers are not going to fix. I want to create a an isolated environment to run them, not touching the global /bin/sh. Commented Sep 12, 2018 at 10:32
  • 1
    Duplicate at askubuntu.com/q/1074295 Both have good answers. Commented Mar 8, 2019 at 9:37

5 Answers 5

3

I suppose mount namespaces or such could be used to arrange for different processes/users to have a different idea of what /bin/sh is.

But that sounds hackish, and could also count as "making permanent changes to the system". It would probably be easier to just make that one-line fix. Make that fix part of your update process, and post a bug report and a patch about the wrong hashbang upstream.

With GNU sed, something like this should do to fix them:

sed -i -e '1s,^#! */bin/sh,#!/bin/bash,' /all/the/scripts/* 
2
  • 1
    It may be a good idea to not replace interpreters whose path have the true prefix /bin/sh (e. g. things like /bin/sh.static): sed -i -re '1s,^#! */bin/sh(\s|$),#!/bin/bash\1,' Commented Sep 12, 2018 at 7:56
  • Thanks for the idea about mount namespaces. With the help of it I've managed to achieve what I originally wanted. See my answer. Commented Sep 12, 2018 at 10:24
3

If /bin/sh -> /bin/dash is a dynamically linked executable on your system as on mine (you can check that with file(1)), you can use a LD_PRELOAD hack for that.

It works like this: A small dynamic library loaded with LD_PRELOAD overrides the glibc's __libc_start_main (the function that calls the executable's main() function), and if argv[0] == /bin/sh then it exec's /bin/bash instead with the same arguments (except for argv[0]); otherwise it calls through to the original __libc_start_main as if nothing had happened.

$ cat sh_is_bash.c #define _GNU_SOURCE /* for RTLD_NEXT */ #include <string.h> #include <unistd.h> #include <dlfcn.h> #include <err.h> int __libc_start_main( int (*main)(int,char**,char**), int ac, char **av, int (*init)(int,char**,char**), void (*fini)(void), void (*rtld_fini)(void), void *stack_end) { typeof(__libc_start_main) *real_lsm; if(ac > 0 && !strcmp(av[0], "/bin/sh")){ av[0] = "/bin/bash"; execv(av[0], av); err(1, "execv %s", av[0]); }else if(real_lsm = dlsym(RTLD_NEXT, "__libc_start_main")) return real_lsm(main, ac, av, init, fini, rtld_fini, stack_end); else errx(1, "BUG: dlsym: %s", dlerror()); } $ cc -fPIC -shared -Wall -W -Wno-parentheses sh_is_bash.c -o sh_is_bash.so -ldl $ LD_PRELOAD=`pwd`/sh_is_bash.so program ... 

Any script with the #! /bin/sh shebang will be executed with /bin/bash instead of /bin/sh when the LD_PRELOAD environment variable contains the absolute path of sh_is_bash.so.

This is ugly, but it does not require any hard changes either to your system or to the scripts, it's easy to deploy & manage, and it doesn't need any special privileges for that.

1
  • Thanks! It worked like a charm. I only needed to add -fPIC and move -ldl to the end of the compilation line. Commented Sep 12, 2018 at 13:08
3

You can easily fix them, don't break your system!

find . -name '*.sh' -type f -exec sed -i '1s|^#! */bin/sh|#!/bin/bash|' {} + 
2
  • Maybe not such a good idea to do this in general. It could well be that there is a program somewhere which is supposed to run under sh. Also, there might be shell scripts which do not end in .sh. Commented Sep 12, 2018 at 7:45
  • 1
    A restriction to the first line may be appropriate. Furthermore we don’t want to replace interpreters that happen to have /bin/sh as their true path prefix (e. g. things like /bin/sh.static). I would also be a little lenient with white-space between the shebang and the interpreter path (which I saw in the wild and which is permitted by some systems). Summary: sed -i -re '1s,^#!\s*/bin/sh(\s|$),#!/bin/bash\1,' Commented Sep 12, 2018 at 7:53
1

I've managed to achieve somewhat close to what I initially wanted by using mount namespaces. (My original solution used unionfs as well, but as it turned out it's not needed at all). It is used to bind-mount /bin/bash to /bin/sh for a limited set of processes. The short procedure to set up a new shell, where sh is bash, is described below.

First we start new shell with an isolated mount namespace:

sudo unshare -m sudo -u user /bin/bash 

And then in the new shell we bind-mount /bin/bash to /bin/sh:

sudo mount --bind /bin/bash /bin/sh 

That's it!

Let's see what we've got in this shell:

user@ubuntu:~$ /bin/sh --version GNU bash, version ... user@ubuntu:~$ diff -s /bin/sh /bin/bash Files /bin/sh and /bin/bash are identical 

But if running in another shell:

user@ubuntu:~$ /bin/sh --version /bin/sh: 0: Illegal option -- user@ubuntu:~$ diff -s /bin/sh /bin/bash Binary files /bin/sh and /bin/bash differ user@ubuntu:~$ diff -s /bin/sh /bin/dash Files /bin/sh and /bin/dash are identical 
9
  • All that to accommodate blatantly wrong software? Because something like But if running in another terminal: ... is just going to create confusion. Commented Sep 12, 2018 at 10:57
  • @AndrewHenle, could you please explain what exactly creates confusion? Commented Sep 12, 2018 at 11:19
  • "But if running in another terminal" - that means "sometimes it works, sometimes it doesn't". That's inherently confusing and it's not something I'd want to put my name on because the best possible reaction is going to be "Why can't it work all the time?" Commented Sep 12, 2018 at 11:30
  • I think you probably didn't get it. The described procedure creates an isolated environment with sh pointing to bash, while not touching the original system. This is what 'another terminal' part is about. Commented Sep 12, 2018 at 11:35
  • 1
    If you want to use namespaces, then please notice that you don't need unionfs; you can simply mount --bind /bin/bash /bin/dash inside the namespace, because mount --bind works with regular files, too. Commented Sep 14, 2018 at 9:56
0

Scripts that contain non-POSIX extensions should have the related correct #! header, in your case:

#!/bin/bash 

so I see no way from editing all incorrect scripts.

BTW: if you are really sure, you could create a temporary link to bash and rename it:

cd /bin ln -s bash nsh mv nsh sh 

Since mv works atomically, this will make sure that there always is a working /bin/sh

As a result, scripts and other shells currently running while you do the rename would continue to work and after the rename, this would call bash instead of dash.

If your system however runs in a way that allows to edit the scripts, I would rather only edit the scripts.

If you replace /bin/sh with a link to bash, do not forget to fix this to make /bin/sh be a link to dash again after you are done.

If the scripts in question are part of a binary packet, don't forget to make a bug report against that problem to your upstream.

2
  • I assume that /bin/sh is linked to dash instead of bash for a reason (i.e. performance). So at least I would want to change the link back after running the scripts. Also, this change would be visible to other processes, and possibly break them, and I would like to avoid this as well. Anyway, thanks. I understand, that the problem may not have an ideal solution. Commented Sep 11, 2018 at 14:42
  • See this question for background information on dash v. bash in Ubuntu. Commented Sep 11, 2018 at 15:11

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.