0

I would like to make a script which allow me to execute a command which inherit environment variables from any PID. Here the script I made :

#!/bin/sh

VARS=$(cat -A /proc/1/environ | tr "^@" "\n")
COMMAND=""

# sh compatible loop on a variable containing multiple lines
printf %s "$VARS" | while IFS='\n' read -r var
do
    if [ "$var" != "" ]; then
        export "$var"
    fi
done

exec "$@"

I though exported variables would be available for the child process (created by exec) but this is obviously not the case because sh my_script.sh printenv doesn't show environment variables which are in /proc/1/environ.

I also tried the following script :

#!/bin/sh

VARS=$(cat -A /proc/1/environ | tr "^@" "\n")
COMMAND=""

# sh compatible loop on a variable containing multiple lines
printf %s "$VARS" | while IFS='\n' read -r var
do
    if [ "$var" != "" ]; then
        # Replace 'VAR=var' by 'VAR="var"' for eval
        # sed replace only the first occurence of the '=' due of the missing /g parameter
        escaped=$(echo $var | sed -e 's/=/="/')\"
        COMMAND="${COMMAND} ${escaped}"
    fi
done

COMMAND="${COMMAND} $@"
eval $COMMAND

However, it looks like eval doesn't export variables even if the evaluated command looks like VAR=value my_command.

How I am supposed to achieve my needs ?

Thanks in advance

Lethgir
  • 1
  • 1
  • 2
  • 1
    To replace NUL chars I think you want `tr "\0" "\n"` – stark Sep 02 '20 at 16:02
  • Piping to your `while read` loop is the problem. See [BashFAQ #42](http://mywiki.wooledge.org/BashFAQ/024). – Charles Duffy Sep 02 '20 at 17:43
  • @CharlesDuffy I tried with the named pipe (according to workaround POSIX compatible in the BashFAQ) but I still can't see my environment variable. My new code is : ```bash #!/bin/sh VARS=$(cat /proc/1/environ | tr "\n" " " | tr "\0" "\n") mkfifo mypipe echo "$VARS" > mypipe & # sh compatible loop on a variable containing multiple lines while IFS='\n' read -r var do if [ "$var" != "" ]; then export "$var" fi done < mypipe rm mypipe exec "$@" ``` – Lethgir Sep 03 '20 at 09:13
  • For one thing, `IFS='\n'` is setting two separator characters, the backslash and `n`; it's not setting newline literals. Honestly, though, if you only had `sh`, I would write this in a completely different language -- C, Go, Python, etc -- rather than build a knowingly-buggy version that can't handle all environment variables correctly. – Charles Duffy Sep 03 '20 at 11:25
  • That said, I'll come back and take a closer look when I've had some coffee and am sitting at a computer. – Charles Duffy Sep 03 '20 at 11:26
  • (Similarly, `tr` operates a byte at a time; you can't ask it to modify `^@` without it individually changing both instances of `^` and instances of `@`) – Charles Duffy Sep 03 '20 at 11:28
  • ...and `COMMAND="${COMMAND} $@"; eval $COMMAND` is going to corrupt the command you're running badly. For a simple example, try passing `printf '%s\n' "first line" "second line"` though it. – Charles Duffy Sep 03 '20 at 11:30
  • Oh, and your code is _still_ running `printf %s "$VARS" | ...`, which means `...` is still in a subshell, which means you haven't addressed the problem that the linked duplicate describes how to fix. _Variable modifications never exit a subshell to be accessible to the parent process_; that includes new values you assign to `COMMAND`. – Charles Duffy Sep 03 '20 at 11:50
  • It's not tested, but https://gist.github.com/charles-dyfis-net/e4533462b384cca04b36d8ced3349f3d is something that at least _looks like_ it ought to work. Note that `'\n'` is special to `tr` because `tr` goes out of its way to do backslash interpretation itself; it's _not_ special in IFS, because IFS interpretation does not. – Charles Duffy Sep 03 '20 at 11:59
  • s/not tested/tested with `dash` on an otherwise full-featured distro/ – Charles Duffy Sep 03 '20 at 12:03
  • It works like a charm and looks better than what I've done ! Thanks for the help and very precise explanations ! – Lethgir Sep 03 '20 at 12:48

1 Answers1

0

That one should work (tested on RHEL 7)

#!/bin/bash

locPROC=$1
locCMD=$2

if [[ -z $locPROC || -z $locCMD ]]; then
        exit
fi

if [[ -r /proc/${locPROC}/environ ]]; then
    while IFS= read -r -d '' line; do
        #Making sure it's properly quoted
        locVar="${line/=/=\"}\""
        #You probably don't want to mess with those
        if [[ ${locVar:0:1} != "_" && ${locVar} != A__z* ]]; then
            eval "$locVar"
            eval "export ${locVar%%=*}"
        fi
    done < "/proc/${locPROC}/environ"

    $locCMD
else
    echo "Environment file is either inexistant or unreadable"
fi

EDITED : According to comments (still use eval...got to read more :) )

Andre Gelinas
  • 912
  • 1
  • 8
  • 10
  • The `<<<` is an unfortunate decision. Much more reliable to use `while IFS= read -r -d '' line; do` and `done <"/proc/$locPROC/environ"` -- then you aren't relying on `cat -A` munging things and can instead have bash directly read the NUL-delimited file in its original format. – Charles Duffy Sep 02 '20 at 17:44
  • Also, there are __much__ better and safer ways to perform indirect assignments than using `eval`. You don't want a variable that was previously set in a manner akin to `myVar='$(rm -rf ~)'` to cause `rm` to be run when you're trying to assign it. – Charles Duffy Sep 02 '20 at 17:46
  • Playing with IFS is one of my pet peeves...but yeah if you think it's better, you know more than me on that Charles. The eval is on the whole string including the "=" isn't it, not the value...but then again indirect assignment in Bash is not my forte (actually didn't think it was possible like "nameref" in Ksh). – Andre Gelinas Sep 02 '20 at 17:50
  • bash has had namerefs since version 4.2; but you could do indirect assignment even without them, for example, `printf -v "$varname" %s "$value"`. See [BashFAQ #6](https://mywiki.wooledge.org/BashFAQ/006#Assigning_indirect.2Freference_variables) – Charles Duffy Sep 02 '20 at 17:51
  • ...and re: changing `IFS`, the `while IFS= read ...` approach scopes the change to the `read` so it doesn't have effect on any other code. – Charles Duffy Sep 02 '20 at 17:52
  • The other thing is that `-d "^@"` terminates on `^` and ignores the `@` entirely (which is why you have code later than _deletes_ the `@`s, but this means that any environment variable with a `^` in its value -- even when not followed by a `@` -- is going to be cut off at that point). – Charles Duffy Sep 02 '20 at 18:05
  • @AndreGelinas Thanks for your answer ! However I must use `sh` and not `bash` :( I will try to edit your code and make it works with sh – Lethgir Sep 03 '20 at 09:11