0

I have a script that works fine in sh on a linux host as well as inside an alpine container. But when I try executing that using docker exec <containerID> sh -c "<script>" it misbehaves. The script's function is to output stuff similar to ps.

systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=$(dirname $c); name=$(grep Name: $d/status); pid=$(basename $d); uid=$(grep Uid: $d/status); uid=$(echo ${uid#Uid:} | xargs); uid=${uid%% *}; user=$(grep :$uid:[0-9] /etc/passwd); user=${user%%:*}; cmdline=$(cat $c|xargs -0 echo); starttime=$(($(awk '{print $22}' $d/stat) / systick)); uptime=$(awk '{print int($1)}' /proc/uptime); elapsed=$(($uptime-$starttime)); echo $pid $user $elapsed $cmdline; done

EDIT: sh -c "<script>" has the same behavior.

4

2 回答 2

2

You are not able to run this script from docker exec because the variables will be interpolated before they sent to the container (i.e., you are going to get values from your local machine, not from within the container).

In order to run it as you wish, you need to replace $ with \$ for every occurrence of $ in your script.

What might work better is to put your script into a file, then map the file to a location within the container, using -v (i.e., -v script.sh:/path/to/script.sh), and call the script via docker exec /path/to/script.sh

于 2020-03-04T23:32:54.627 回答
1

Part 1: A Working Answer

A Working One-Liner (Quoted For Use By Docker)

getProcessDataDef='shellQuoteWordsDef='"'"'shellQuoteWords() { sq="'"'"'"'"'"'"'"'"'"; dq='"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'; for arg; do printf "'"'"'"'"'"'"'"'"'%s'"'"'"'"'"'"'"'"' " "$(printf '"'"'"'"'"'"'"'"'%s\n'"'"'"'"'"'"'"'"' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"; done; printf '"'"'"'"'"'"'"'"'\n'"'"'"'"'"'"'"'"'; }'"'"'; shellQuoteNullSeparatedStream() { xargs -0 sh -c "${shellQuoteWordsDef};"'"'"' shellQuoteWords "$@"'"'"' _; }; getProcessData() { systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=${c%/*}; pid=${d##*/}; name=$(awk '"'"'/^Name:/ { print $2 }'"'"' <"$d"/status); uid=$(awk '"'"'/^Uid:/ { print $2 }'"'"' <"$d"/status); pwent=$(getent passwd "$uid"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <"$c"); starttime=$(awk -v systick="$systick" '"'"'{print int($22 / systick)}'"'"' "$d"/stat); uptime=$(awk '"'"'{print int($1)}'"'"' /proc/uptime); elapsed=$((uptime-starttime)); echo "$pid $user $elapsed $cmdline"; done; }; getProcessData'
sh -c "$getProcessDataDef"  # or docker exec <container> sh -c "$getProcessDataDef"

A Working One-Liner (Before Quoting/Escaping)

shellQuoteWordsDef='shellQuoteWords() { sq="'"'"'"; dq='"'"'"'"'"'; for arg; do printf "'"'"'%s'"'"' " "$(printf '"'"'%s\n'"'"' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"; done; printf '"'"'\n'"'"'; }'; shellQuoteNullSeparatedStream() { xargs -0 sh -c "${shellQuoteWordsDef};"' shellQuoteWords "$@"' _; }; getProcessData() { systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=${c%/*}; pid=${d##*/}; name=$(awk '/^Name:/ { print $2 }' <"$d"/status); uid=$(awk '/^Uid:/ { print $2 }' <"$d"/status); pwent=$(getent passwd "$uid"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <"$c"); starttime=$(awk -v systick="$systick" '{print int($22 / systick)}' "$d"/stat); uptime=$(awk '{print int($1)}' /proc/uptime); elapsed=$((uptime-starttime)); echo "$pid $user $elapsed $cmdline"; done; }; getProcessData "$@"

What Went Into That One-Liner

shellQuoteWordsDef='shellQuoteWords() { sq="'"'"'"; dq='"'"'"'"'"'; for arg; do printf "'"'"'%s'"'"' " "$(printf '"'"'%s\n'"'"' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"; done; printf '"'"'\n'"'"'; }'

shellQuoteNullSeparatedStream() {
  xargs -0 sh -c "${shellQuoteWordsDef};"' shellQuoteWords "$@"' _
}

getProcessData() {
  systick=$(getconf CLK_TCK)
  for c in /proc/*/cmdline; do
    d=${c%/*}; pid=${d##*/}
    name=$(awk '/^Name:/ { print $2 }' <"$d"/status)
    uid=$(awk '/^Uid:/ { print $2 }' <"$d"/status)
    pwent=$(getent passwd "$uid")
    user=${pwent%%:*}
    cmdline=$(shellQuoteNullSeparatedStream <"$c")
    starttime=$(awk -v systick="$systick" '{print int($22 / systick)}' "$d"/stat)
    uptime=$(awk '{print int($1)}' /proc/uptime)
    elapsed=$((uptime-starttime))
    echo "$pid $user $elapsed $cmdline"
  done
}

What Went Into The Shell-Quoting Helper Used By That One-Liner

To allow easier reading and editing, the function stringified above looks like:

# This is the function we're including in our code passed to xargs in-band above:
shellQuoteWords() {
  sq="'"; dq='"'
  for arg; do
    printf "'%s' " "$(printf '%s\n' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"
  done
  printf '\n'
}

Part 2: How That Answer Was Created

Python has an excellent shlex.quote() function (or pipes.quote() in Python 2) that can be used to generate a shell-quoted version of a string. In this context, that can be used as follows:

Python 3.7.6 (default, Feb 27 2020, 15:15:00)
[Clang 7.1.0 (tags/RELEASE_710/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s = r'''
... shellQuoteWords() {
...   sq="'"; dq='"'
...   for arg; do
...     printf "'%s' " "$(printf '%s\n' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"
...   done
...   printf '\n'
... }
... '''
>>> import shlex
>>> print(shlex.quote(s))
'
shellQuoteWords() {
  sq="'"'"'"; dq='"'"'"'"'"'
  for arg; do
    printf "'"'"'%s'"'"' " "$(printf '"'"'%s\n'"'"' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"
  done
  printf '"'"'\n'"'"'
}
'

That result is itself a perfectly valid string in shell. That is to say, one can run:

s='
shellQuoteWords() {
  sq="'"'"'"; dq='"'"'"'"'"'
  for arg; do
    printf "'"'"'%s'"'"' " "$(printf '"'"'%s\n'"'"' "$arg" | sed -e "s@${sq}@${sq}${dq}${sq}${dq}${sq}@g")"
  done
  printf '"'"'\n'"'"'
}
'
eval "$s"
shellQuoteWords "hello world" 'hello world' "hello 'world'" 'hello "world"'

...and get completely valid output.

The same process was followed to generate a string that evaluated to the definition of getProcessData.

于 2020-03-18T01:41:31.060 回答