Subject: Scripts in `expect' package enter an endless `sh exec' loop.

From: "W. Kosior" <[email-masked]>
Date: Fri, 30 May 2025 10:25:48 +0200

I wanted to use the `unbuffer' command from `expect' package.
`unbuffer' can be used to run another command in a way it sees its
STDOUT as a terminal.

Even the simplest invokation

    guix shell expect -- unbuffer echo test

freezes without printing "test" as it should.

I found that the initial lines in all `expect's executable script files
contain an extra `exec sh' line that causes the script to enter an
endless recursion when interpreted by a POSIX shell.

--8<---------------cut here---------------start------------->8---
$ head -6 $(file $(guix build expect)/bin/* | grep text | sed 's/^\([^:]\+\).*/\1/')
==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/autoexpect <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/autopasswd <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/cryptdir <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/decryptdir <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/dislocate <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/ftp-rfc <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/kibitz <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/lpunlock <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/mkpasswd <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/multixterm <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/passmass <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/rftp <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/rlogin-cwd <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/timed-read <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/timed-run <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/tknewsbiff <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/tkpasswd <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/unbuffer <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/weather <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/xkibitz <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

==> /gnu/store/bwix93j3pysx464sh4mp98lsjgsbpcri-expect-5.45.4/bin/xpstat <==
#!/gnu/store/m0xdsa8cfq6mq1kxgxmpmpg71la4f0b9-bash-minimal-5.1.16/bin/sh
# \
exec sh "$0" ${1+"$@"}
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}
--8<---------------cut here---------------end--------------->8---

An artifact of incorrect shebang patching, perhaps?  Nonetheless, as
one might guess, giving the script directly to tclsh makes it work:

--8<---------------cut here---------------start------------->8---
$ guix shell tcl expect bash -- bash -c 'tclsh $(which unbuffer) echo test'
test
--8<---------------cut here---------------end--------------->8---

I am using `expect' from Guix d3d157bc61c4a6a3fac11e33d26f6f2a72a24151
from May 21, 2025.

Best!

--
W. Kosior

website: https://koszko.org/koszko.html
fediverse: https://friendica.me/profile/koszko/profile
PGP fingerprint: E972 7060 E3C5 637C 8A4F  4B42 4BC5 221C 5A79 FD1A