Repository: Gallopsled/pwntools

Issue:
`pwnlib.misc.util.sh_string` broken for newline
If you pass _only_ a newline to `sh_string`, the result is incorrect.  This may affect other input.

``` py
>>> with open('\n','w+') as f: f.write("HELLO\n")
>>> exitcode = os.system("cat '\n'")
HELLO
>>> exitcode = os.system("cat %s" % sh_string('\n'))
cat: : No such file or directory
```

`pwnlib.misc.util.sh_string` broken for newline
If you pass _only_ a newline to `sh_string`, the result is incorrect.  This may affect other input.

``` py
>>> with open('\n','w+') as f: f.write("HELLO\n")
>>> exitcode = os.system("cat '\n'")
HELLO
>>> exitcode = os.system("cat %s" % sh_string('\n'))
cat: : No such file or directory
```

`pwnlib.misc.util.sh_string` broken for newline
If you pass _only_ a newline to `sh_string`, the result is incorrect.  This may affect other input.

``` py
>>> with open('\n','w+') as f: f.write("HELLO\n")
>>> exitcode = os.system("cat '\n'")
HELLO
>>> exitcode = os.system("cat %s" % sh_string('\n'))
cat: : No such file or directory
```



Discussion:
This appears to still be broken, but doesn't matter much because `ssh.process` is significantly more powerful, reducing reliance on `sh_string` with `ssh.run`.

This is a problem with sh that I am not sure we can overcome.

Try doing the following two:

``` sh
echo "$(echo a)"
echo "$(echo -n a)"
```

It seems that sh will always eat the trailing newline for convenience. Unless anybody has any good ideas, I think we should just add this as a limitation in the documentation.

Alternatively we could just add an actual newline in the output, but we would also need to document this behavior.

However, the following works fine:

```
$ echo $'a\n'  | xxd
0000000: 610a 0a                                  a..
```

Right, we can fix it in bash then, however that syntax does not work in a pure sh shell.

```
$ sh
sh-3.2$ echo $'a\n' | xxd
0000000: 610a 0a                                  a..
```

Can you find me a quote on that being within the shell spec? It does not work in the openbsd shell.

It should work, but I don't have a mechanism to test OpenBSD.

OpenBSD docs don't mention it
http://man.openbsd.org/cgi-bin/man.cgi?query=sh.

FreeBSD docs say it should work
https://www.freebsd.org/cgi/man.cgi?query=sh.

```
 Dollar-Single Quotes
     Enclosing characters between $' and ' preserves the literal mean-
     ing of all characters except backslashes and single quotes.  A
     backslash introduces a C-style escape sequence:
```

...

```
     \n      Newline
```

_Zach Riggle_

On Wed, Aug 17, 2016 at 8:18 PM, Mathias Svensson notifications@github.com
wrote:

> Can you find me a quote on that being within the shell spec? It does not
> work in the openbsd shell.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240613578,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GK-Quo81BwYy2cgsUn9mjNrKN4Qmks5qg88agaJpZM4DX9c4
> .

The string literal syntax $'\n' is not supported on openbsd. I have just double-checked.

It seems that printf with octal escape sequences is the most portable option. It works on every machine that I have access to.

However I have still been unable to create a bash variable that contains a newline at then end without using $'\n'. I have tried the following

``` sh
X1=$(printf '\012\012')
X2="$(printf '\012\012')"
X3=`printf '\012\012'`
X4="`printf '\012\012'`"
echo -n "$X1$X2$X3$X4" | xxd
```

Do you have any ideas?

I think with classic sh you can just embed the literal rather than escape
sequence
On Thu, Aug 18, 2016 at 12:17 AM Mathias Svensson notifications@github.com
wrote:

> It seems that printf with octal escape sequences is the most portable
> option. It works on every machine that I have access to.
> 
> However I have still been unable to create a bash variable that contains a
> newline at then end without using $'\n'. I have tried the following
> 
> X1=$(printf '\012\012')
> X2="$(printf '\012\012')"
> X3=`printf '\012\012'`
> X4="`printf '\012\012'`"echo -n "$X1$X2$X3$X4" | xxd
> 
> Do you have any ideas?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240643263,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GLehw02UH4mNpERY2dQl_hj4f2u9ks5qhAbugaJpZM4DX9c4
> .

From [stack overflow](https://stackoverflow.com/questions/15184358/how-to-avoid-bash-command-substitution-to-remove-the-newline-character), we should use something like this:

``` sh
a=$(printf 'test\n\n'; printf x); a=${a%x}
```

It seems to be posix compliant.

However this means that the goal of sh_string seems to be fundamentally impossible to do if the string contains trailing newlines.

This appears to still be broken, but doesn't matter much because `ssh.process` is significantly more powerful, reducing reliance on `sh_string` with `ssh.run`.

This is a problem with sh that I am not sure we can overcome.

Try doing the following two:

``` sh
echo "$(echo a)"
echo "$(echo -n a)"
```

It seems that sh will always eat the trailing newline for convenience. Unless anybody has any good ideas, I think we should just add this as a limitation in the documentation.

Alternatively we could just add an actual newline in the output, but we would also need to document this behavior.

However, the following works fine:

```
$ echo $'a\n'  | xxd
0000000: 610a 0a                                  a..
```

Right, we can fix it in bash then, however that syntax does not work in a pure sh shell.

```
$ sh
sh-3.2$ echo $'a\n' | xxd
0000000: 610a 0a                                  a..
```

Can you find me a quote on that being within the shell spec? It does not work in the openbsd shell.

It should work, but I don't have a mechanism to test OpenBSD.

OpenBSD docs don't mention it
http://man.openbsd.org/cgi-bin/man.cgi?query=sh.

FreeBSD docs say it should work
https://www.freebsd.org/cgi/man.cgi?query=sh.

```
 Dollar-Single Quotes
     Enclosing characters between $' and ' preserves the literal mean-
     ing of all characters except backslashes and single quotes.  A
     backslash introduces a C-style escape sequence:
```

...

```
     \n      Newline
```

_Zach Riggle_

On Wed, Aug 17, 2016 at 8:18 PM, Mathias Svensson notifications@github.com
wrote:

> Can you find me a quote on that being within the shell spec? It does not
> work in the openbsd shell.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240613578,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GK-Quo81BwYy2cgsUn9mjNrKN4Qmks5qg88agaJpZM4DX9c4
> .

The string literal syntax $'\n' is not supported on openbsd. I have just double-checked.

It seems that printf with octal escape sequences is the most portable option. It works on every machine that I have access to.

However I have still been unable to create a bash variable that contains a newline at then end without using $'\n'. I have tried the following

``` sh
X1=$(printf '\012\012')
X2="$(printf '\012\012')"
X3=`printf '\012\012'`
X4="`printf '\012\012'`"
echo -n "$X1$X2$X3$X4" | xxd
```

Do you have any ideas?

I think with classic sh you can just embed the literal rather than escape
sequence
On Thu, Aug 18, 2016 at 12:17 AM Mathias Svensson notifications@github.com
wrote:

> It seems that printf with octal escape sequences is the most portable
> option. It works on every machine that I have access to.
> 
> However I have still been unable to create a bash variable that contains a
> newline at then end without using $'\n'. I have tried the following
> 
> X1=$(printf '\012\012')
> X2="$(printf '\012\012')"
> X3=`printf '\012\012'`
> X4="`printf '\012\012'`"echo -n "$X1$X2$X3$X4" | xxd
> 
> Do you have any ideas?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240643263,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GLehw02UH4mNpERY2dQl_hj4f2u9ks5qhAbugaJpZM4DX9c4
> .

From [stack overflow](https://stackoverflow.com/questions/15184358/how-to-avoid-bash-command-substitution-to-remove-the-newline-character), we should use something like this:

``` sh
a=$(printf 'test\n\n'; printf x); a=${a%x}
```

It seems to be posix compliant.

However this means that the goal of sh_string seems to be fundamentally impossible to do if the string contains trailing newlines.

This appears to still be broken, but doesn't matter much because `ssh.process` is significantly more powerful, reducing reliance on `sh_string` with `ssh.run`.

This is a problem with sh that I am not sure we can overcome.

Try doing the following two:

``` sh
echo "$(echo a)"
echo "$(echo -n a)"
```

It seems that sh will always eat the trailing newline for convenience. Unless anybody has any good ideas, I think we should just add this as a limitation in the documentation.

Alternatively we could just add an actual newline in the output, but we would also need to document this behavior.

However, the following works fine:

```
$ echo $'a\n'  | xxd
0000000: 610a 0a                                  a..
```

Right, we can fix it in bash then, however that syntax does not work in a pure sh shell.

```
$ sh
sh-3.2$ echo $'a\n' | xxd
0000000: 610a 0a                                  a..
```

Can you find me a quote on that being within the shell spec? It does not work in the openbsd shell.

It should work, but I don't have a mechanism to test OpenBSD.

OpenBSD docs don't mention it
http://man.openbsd.org/cgi-bin/man.cgi?query=sh.

FreeBSD docs say it should work
https://www.freebsd.org/cgi/man.cgi?query=sh.

```
 Dollar-Single Quotes
     Enclosing characters between $' and ' preserves the literal mean-
     ing of all characters except backslashes and single quotes.  A
     backslash introduces a C-style escape sequence:
```

...

```
     \n      Newline
```

_Zach Riggle_

On Wed, Aug 17, 2016 at 8:18 PM, Mathias Svensson notifications@github.com
wrote:

> Can you find me a quote on that being within the shell spec? It does not
> work in the openbsd shell.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240613578,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GK-Quo81BwYy2cgsUn9mjNrKN4Qmks5qg88agaJpZM4DX9c4
> .

The string literal syntax $'\n' is not supported on openbsd. I have just double-checked.

It seems that printf with octal escape sequences is the most portable option. It works on every machine that I have access to.

However I have still been unable to create a bash variable that contains a newline at then end without using $'\n'. I have tried the following

``` sh
X1=$(printf '\012\012')
X2="$(printf '\012\012')"
X3=`printf '\012\012'`
X4="`printf '\012\012'`"
echo -n "$X1$X2$X3$X4" | xxd
```

Do you have any ideas?

I think with classic sh you can just embed the literal rather than escape
sequence
On Thu, Aug 18, 2016 at 12:17 AM Mathias Svensson notifications@github.com
wrote:

> It seems that printf with octal escape sequences is the most portable
> option. It works on every machine that I have access to.
> 
> However I have still been unable to create a bash variable that contains a
> newline at then end without using $'\n'. I have tried the following
> 
> X1=$(printf '\012\012')
> X2="$(printf '\012\012')"
> X3=`printf '\012\012'`
> X4="`printf '\012\012'`"echo -n "$X1$X2$X3$X4" | xxd
> 
> Do you have any ideas?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> https://github.com/Gallopsled/pwntools/issues/455#issuecomment-240643263,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAG0GLehw02UH4mNpERY2dQl_hj4f2u9ks5qhAbugaJpZM4DX9c4
> .

From [stack overflow](https://stackoverflow.com/questions/15184358/how-to-avoid-bash-command-substitution-to-remove-the-newline-character), we should use something like this:

``` sh
a=$(printf 'test\n\n'; printf x); a=${a%x}
```

It seems to be posix compliant.

However this means that the goal of sh_string seems to be fundamentally impossible to do if the string contains trailing newlines.


Resolving patch:
diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py
--- a/pwnlib/elf/elf.py
+++ b/pwnlib/elf/elf.py
@@ -312,10 +312,9 @@ def _populate_libraries(self):
             return
 
         try:
-            cmd = 'ulimit -s unlimited; LD_TRACE_LOADED_OBJECTS=1 LD_WARN=1 LD_BIND_NOW=1 %s 2>/dev/null'
-            arg = misc.sh_string(self.path)
+            cmd = misc.sh_command_with('ulimit -s unlimited; LD_TRACE_LOADED_OBJECTS=1 LD_WARN=1 LD_BIND_NOW=1 %s 2>/dev/null', self.path)
 
-            data = subprocess.check_output(cmd % (arg), shell = True, stderr = subprocess.STDOUT)
+            data = subprocess.check_output(cmd, shell = True, stderr = subprocess.STDOUT)
             libs = misc.parse_ldd_output(data)
 
             for lib in dict(libs):
diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py
--- a/pwnlib/tubes/ssh.py
+++ b/pwnlib/tubes/ssh.py
@@ -83,16 +83,17 @@ def __init__(self, parent, process = None, tty = False, wd = None, env = None, t
         msg = 'Opening new channel: %r' % (process or 'shell')
 
         if isinstance(process, (list, tuple)):
-            process = ' '.join(misc.sh_string(s) for s in process)
+            fmt = ' '.join('%s' for s in process)
+            process = misc.sh_command_with(fmt, *process)
 
         if process and wd:
-            process = "cd %s >/dev/null 2>&1; %s" % (misc.sh_string(wd), process)
+            process = misc.sh_command_with('cd %s >/dev/null 2>&1;', wd) + process
 
         if process and env:
             for name, value in env.items():
                 if not re.match('^[a-zA-Z_][a-zA-Z0-9_]*$', name):
                     self.error('run(): Invalid environment key $r' % name)
-                process = 'export %s=%s; %s' % (name, misc.sh_string(value), process)
+                process = '%s;%s' % (misc.sh_prepare(name, value, export=True), process)
 
         if process and tty:
             if raw:
@@ -847,7 +848,7 @@ def is_exe(path):
 os.execve(exe, argv, os.environ)
 """ % locals()
 
-        script = script.lstrip()
+        script = script.strip()
 
         self.debug("Created execve script:\n" + script)
 
@@ -875,9 +876,9 @@ def is_exe(path):
             if not aslr:
                 self.warn_once("ASLR is disabled!")
 
-            script = misc.sh_string(script)
+            script = misc.sh_command_with('for py in python2.7 python2 python; do test -x "$(which $py 2>&1)" && exec $py -c %s check; done; echo 2', script)
             with context.local(log_level='error'):
-                python = self.run('test -x "$(which python 2>&1)" && exec python -c %s check; echo 2' % script, raw=raw)
+                python = self.run(script, raw=raw)
             result = safeeval.const(python.recvline())
 
             # If an error occurred, try to grab as much output
@@ -1146,8 +1147,8 @@ def close(self):
     def _libs_remote(self, remote):
         """Return a dictionary of the libraries used by a remote file."""
         cmd = '(ulimit -s unlimited; ldd %s > /dev/null && (LD_TRACE_LOADED_OBJECTS=1 %s || ldd %s)) 2>/dev/null'
-        arg = misc.sh_string(remote)
-        data, status = self.run_to_end(cmd % (arg, arg, arg))
+        cmd = misc.sh_command_with(lambda arg: cmd % (arg, arg, arg), remote)
+        data, status = self.run_to_end(cmd)
         if status != 0:
             self.error('Unable to find libraries for %r' % remote)
             return {}
@@ -1155,8 +1156,8 @@ def _libs_remote(self, remote):
         return misc.parse_ldd_output(data)
 
     def _get_fingerprint(self, remote):
-        arg = misc.sh_string(remote)
-        cmd = '(openssl sha256 || sha256 || sha256sum) 2>/dev/null < %s' % (arg)
+        cmd = '(openssl sha256 || sha256 || sha256sum) 2>/dev/null < %s'
+        cmd = misc.sh_command_with(cmd, remote)
         data, status = self.run_to_end(cmd)
 
         if status != 0:
@@ -1199,7 +1200,8 @@ def update(has, total):
             self.sftp.get(remote, local, update)
             return
 
-        total, exitcode = self.run_to_end('wc -c <' + misc.sh_string(remote))
+        cmd = misc.sh_command_with('wc -c < %s', remote)
+        total, exitcode = self.run_to_end(cmd)
 
         if exitcode != 0:
             h.failure("%r does not exist or is not accessible" % remote)
@@ -1208,7 +1210,8 @@ def update(has, total):
         total = int(total)
 
         with context.local(log_level = 'ERROR'):
-            c = self.run('cat ' + misc.sh_string(remote))
+            cmd = misc.sh_command_with('cat < %s', remote)
+            c = self.run(cmd)
         data = ''
 
         while True:
@@ -1318,7 +1321,7 @@ def download_dir(self, remote=None, local=None):
             remote = str(self.sftp.normalize(remote))
         else:
             with context.local(log_level='error'):
-                remote = self.system('readlink -f %s' % remote)
+                remote = self.system(misc.sh_command_with('readlink -f %s', remote))
 
         dirname  = os.path.dirname(remote)
         basename = os.path.basename(remote)
@@ -1330,7 +1333,7 @@ def download_dir(self, remote=None, local=None):
 
         with context.local(log_level='error'):
             remote_tar = self.mktemp()
-            tar = self.system('tar -C %s -czf %s %s' % (dirname, remote_tar, basename))
+            tar = self.system(misc.sh_command_with('tar -C %s -czf %s %s', dirname, remote_tar, basename))
 
             if 0 != tar.wait():
                 self.error("Could not create remote tar")
@@ -1374,7 +1377,8 @@ def upload_data(self, data, remote):
                 return
 
         with context.local(log_level = 'ERROR'):
-            s = self.run('cat>' + misc.sh_string(remote), tty=False)
+            cmd = misc.sh_command_with('cat>%s', remote)
+            s = self.run(cmd, tty=False)
             s.send(data)
             s.shutdown('send')
             data   = s.recvall()
@@ -1457,7 +1461,7 @@ def download(self, file_or_directory, remote=None):
         if not self.sftp:
             self.error("Cannot determine remote file type without SFTP")
 
-        if 0 == self.system('test -d %s' % file_or_directory).wait():
+        if 0 == self.system(misc.sh_command_with('test -d %s', file_or_directory)).wait():
             self.download_dir(file_or_directory, remote)
         else:
             self.download_file(file_or_directory, remote)
@@ -1513,7 +1517,8 @@ def interactive(self, shell=None):
         s = self.shell(shell)
 
         if self.cwd != '.':
-            s.sendline('cd ' + misc.sh_string(self.cwd))
+            cmd = misc.sh_command_with('cd %s', self.cwd)
+            s.sendline(cmd)
 
         s.interactive()
         s.close()
@@ -1554,7 +1559,8 @@ def set_working_directory(self, wd = None):
                 self.error("Could not generate a temporary directory (%i)\n%s" % (status, wd))
 
         else:
-            _, status = self.run_to_end('ls ' + misc.sh_string(wd), wd = '.')
+            cmd = misc.sh_command_with('ls %s', wd)
+            _, status = self.run_to_end(cmd, wd = '.')
 
             if status:
                 self.error("%r does not appear to exist" % wd)
diff --git a/pwnlib/util/misc.py b/pwnlib/util/misc.py
--- a/pwnlib/util/misc.py
+++ b/pwnlib/util/misc.py
@@ -8,6 +8,7 @@
 import string
 
 from . import lists
+from . import fiddling
 from ..context import context
 from ..log import getLogger
 
@@ -290,9 +291,9 @@ def sh_string(s):
         >>> print sh_string("foo\\\\'bar")
         "foo\\\\'bar"
         >>> print sh_string("foo\\x01'bar")
-        "$( (echo Zm9vASdiYXI=|(base64 -d||openssl enc -d -base64)||echo -en 'foo\\x01\\x27bar') 2>/dev/null)"
-        >>> print subprocess.check_output("echo -n " + sh_string("foo\\\\'bar"), shell = True)
-        foo\\'bar
+        "$(printf 'foo\\001\\047bar')"
+        >>> print `subprocess.check_output("echo -n " + sh_string("foo\\x01'bar"), shell = True)`
+        "foo\\x01'bar"
     """
 
     very_good = set(string.ascii_letters + string.digits)
@@ -301,6 +302,8 @@ def sh_string(s):
 
     if '\x00' in s:
         log.error("sh_string(): Cannot create a null-byte")
+    if s.endswith('\n'):
+        log.error("sh_string(): Cannot create a newline-terminated string")
 
     if all(c in very_good for c in s):
         return s
@@ -319,11 +322,111 @@ def sh_string(s):
         for c in s:
             if c == '\\':
                 fixed += '\\\\'
+            elif c == '\n':
+                fixed += '\\n'
             elif c in good:
                 fixed += c
             else:
-                fixed += '\\x%02x' % ord(c)
-        return '"$( (echo %s|(base64 -d||openssl enc -d -base64)||echo -en \'%s\') 2>/dev/null)"' % (base64.b64encode(s), fixed)
+                fixed += '\\%03o' % ord(c)
+        return '"$(printf \'%s\')"' % fixed
+
+def sh_prepare(variables, export = False):
+    """Outputs a posix compliant shell command that will put the data specified
+    by the dictionary into the environment.
+
+    It is assumed that the keys in the dictionary are valid variable names that
+    does not need any escaping.
+
+    Arguments:
+      variables(dict): The variables to set.
+      export(bool): Should the variables be exported or only stored in the shell environment?
+      output(str): A valid posix shell command that will set the given variables.
+
+    It is assumed that `var` is a valid name for a variable in the shell.
+
+    Examples:
+
+        >>> print sh_prepare({'X': 'foobar'})
+        X=foobar
+        >>> r = sh_prepare({'X': 'foobar', 'Y': 'cookies'})
+        >>> r == 'X=foobar;Y=cookies' or r == 'Y=cookies;X=foobar'
+        True
+        >>> print sh_prepare({'X': 'foo bar'})
+        X='foo bar'
+        >>> print sh_prepare({'X': "foo'bar"})
+        X="foo'bar"
+        >>> print sh_prepare({'X': "foo\\\\bar"})
+        X='foo\\bar'
+        >>> print sh_prepare({'X': "foo\\\\'bar"})
+        X="foo\\\\'bar"
+        >>> print sh_prepare({'X': "foo\\x01'bar"})
+        X="$(printf 'foo\\001\\047bar')"
+        >>> print sh_prepare({'X': "foo\\x01'bar"}, export = True)
+        export X="$(printf 'foo\\001\\047bar')"
+        >>> print sh_prepare({'X': "foo\\x01'bar\\n"})
+        X="$(printf 'foo\\001\\047bar\\nx')";X=${X%x}
+        >>> print sh_prepare({'X': "foo\\x01'bar\\n"}, export = True)
+        X="$(printf 'foo\\001\\047bar\\nx')";export X=${X%x}
+        >>> print `subprocess.check_output('%s;echo -n "$X"' % sh_prepare({'X': "foo\\x01'bar"}), shell = True)`
+        "foo\\x01'bar"
+    """
+
+    out = []
+    export = 'export ' if export else ''
+
+    for k, v in variables.items():
+        if v.endswith('\n'):
+            out.append('%s=%s;%s%s=${%s%%x}' % (k, sh_string(v + "x"), export, k, k))
+        else:
+            out.append('%s%s=%s' % (export, k, sh_string(v)))
+    return ';'.join(out)
+
+def sh_command_with(f, *args):
+    """sh_command_with(f, arg0, ..., argN) -> command
+
+    Returns a command create by evaluating `f(new_arg0, ..., new_argN)`
+    whenever `f` is a function and `f % (new_arg0, ..., new_argN)` otherwise.
+
+    If the arguments are purely alphanumeric, then they are simply passed to
+    function. If they are simple to escape, they will be escaped and passed to
+    the function.
+
+    If the arguments contain trailing newlines, then it is hard to use them
+    directly because of a limitation in the posix shell. In this case the
+    output from `f` is prepended with a bit of code to create the variables.
+
+    Examples:
+
+        >>> print sh_command_with(lambda: "echo hello")
+        echo hello
+        >>> print sh_command_with(lambda x: "echo " + x, "hello")
+        echo hello
+        >>> print sh_command_with(lambda x: "echo " + x, "\\x01")
+        echo "$(printf '\\001')"
+        >>> import random
+        >>> random.seed(1)
+        >>> print sh_command_with(lambda x: "echo " + x, "\\x01\\n")
+        dwtgmlqu="$(printf '\\001\\nx')";dwtgmlqu=${dwtgmlqu%x};echo "$dwtgmlqu"
+        >>> random.seed(1)
+        >>> print sh_command_with("echo %s", "\\x01\\n")
+        dwtgmlqu="$(printf '\\001\\nx')";dwtgmlqu=${dwtgmlqu%x};echo "$dwtgmlqu"
+    """
+
+    args = list(args)
+    out = []
+
+    for n in range(len(args)):
+        if args[n].endswith('\n'):
+            v = fiddling.randoms(8)
+            out.append(sh_prepare({v: args[n]}))
+            args[n] = '"$%s"' % v
+        else:
+            args[n] = sh_string(args[n])
+    if hasattr(f, '__call__'):
+        out.append(f(*args))
+    else:
+        out.append(f % tuple(args))
+    return ';'.join(out)
 
 def dealarm_shell(tube):
     """Given a tube which is a shell, dealarm it.


Test patch: