英文:
Why is expect failing when the docker container is run without --interactive
问题
我在运行一个位于Docker容器内的expect脚本时遇到了错误。
首先,我创建了一个简单的expect脚本test.expect
:
#!/usr/bin/expect -f
set timeout 3
puts "--- Start"
lassign $argv name duration
spawn bash -c "echo Hello ${name} && sleep ${duration}"
expect {
eof {
puts "No timeout";
}
}
puts "--- End"
注意:expect_before
起初被注释掉了。
现在是Docker镜像的Dockerfile
:
FROM alpine:latest
RUN apk update && \
apk upgrade && \
apk add bash expect
COPY test.expect /root
ENV PATH $PATH:/root
WORKDIR /root
ENTRYPOINT ["test.expect"]
现在构建并运行Docker:
$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
脚本的两个路径在Docker内外都正常工作,无论是否使用了-i
(--interactive
)。现在我取消了expect_before
的注释。重新构建和运行:
$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
自然地,在我实际的流水线(GitHub工作流程,调用操作,运行大容器,使用复杂脚本,需要我的实际expect脚本)中第一次看到这个问题时,我假设它与在没有标准输入和/或标准输出的环境中运行expect有关。直到我最终创建了这个简化的测试用例,我才找到了我的错误的真正原因。
我无法理解为什么expect_before
会引起这种行为,更不用说我可以做什么来修复它了。
使用expect -d
并没有给我带来任何启示。
$ docker run test-expect Martin 5
expect version 5.45.4
argv[0] = /usr/bin/expect argv[1] = -df argv[2] = /root/test.expect argv[3] = Martin argv[4] = 5
set argc 2
set argv0 "/root/test.expect"
set argv "Martin 5"
executing commands from command file /root/test.expect
--- Start
spawn bash -c echo Hello Martin && sleep 5
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {7}
expect: read eof
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) ""
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
英文:
I am getting an error when I run an expect script inside a docker container.
First I created a simple expect script test.expect
#!/usr/bin/expect -f
set timeout 3
#expect_before timeout { puts "\nTimeout fired"; exit 1 }
puts "--- Start"
lassign $argv name duration
spawn bash -c "echo Hello ${name} && sleep ${duration} "
expect {
eof {
puts "No timeout";
}
}
puts "--- End"
Note: the expect_before
is commented out at first
Now the docker image Dockerfile
FROM alpine:latest
RUN apk update && \
apk upgrade && \
apk add bash expect
COPY test.expect /root
ENV PATH $PATH:/root
WORKDIR /root
ENTRYPOINT ["test.expect"]
Now build and run the docker:
$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
Both paths through the script work fine with or without docker -i
(--interactive
). Now I uncomment the expect_before
. Rebuild and rerun:
$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
Naturally when I first saw this in my actual pipeline (GitHub workflows, calling Actions, running large containers, with convoluted scripts, that need my actual expect script) I assumed it was related to the fact that expect was being run in an environment where there was no stdin and or stdout. It wasn't until I eventually created this much simpler test case did I find the actual cause of my error.
I cannot fathom why expect_before
causes this behaviour, let alone what I can do to fix it.
Running expect
with -d
did not give me any insight
$ docker run test-expect Martin 5
expect version 5.45.4
argv[0] = /usr/bin/expect argv[1] = -df argv[2] = /root/test.expect argv[3] = Martin argv[4] = 5
set argc 2
set argv0 "/root/test.expect"
set argv "Martin 5"
executing commands from command file /root/test.expect
--- Start
spawn bash -c echo Hello Martin && sleep 5
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {7}
expect: read eof
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) ""
error writing "stdout": bad file number
while executing
"puts "--- End""
(file "/root/test.expect" line 15)
答案1
得分: 2
I stumbled upon the actual answer!
> Rather than add all the alternatives into every expect command in your program, you can make use of expect_before and expect_after. These allow you to set up alternative sequences that every subsequent expect command in the currently spawned process will react to in addition to the ones that you've explicitly written into the expect command. Where the returned sequence from the spawned command matches several alternatives, the order of priority taken is
>
> ~~~
> expect_before
> expect
> expect_after
> ~~~
>
> Now - one word of caution. You should specify any expect_before and expect_after commands AFTER you have spawned the process you wish to control. Expect can control multiple spawned processes, and if you specify your _befores and _afters at the wrong point, you can end up applying them to the wrong process.
>
> If you have no spawned process at all and you expect_before, you're liable to get erratic results - for example, an application which runs interactively but fails from crontab, or one which works on one release of an operating system but fails on the next release. Very hard problem to identify if you're not aware of the risk!
So the fix is really simple:
#!/usr/bin/expect -f
set timeout 3
puts "--- Start"
lassign $argv name duration
spawn bash -c "echo Hello ${name} && sleep ${duration}"
expect_before timeout { puts "\nTimeout fired"; exit 1 }
expect {
eof {
puts "No timeout";
}
}
puts "--- End"
And now it always works regardless of the -i
flag
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
英文:
I stumbled upon the actual answer!
> Rather than add all the alternatives into every expect command in your program, you can make use of expect_before and expect_after. These allow you to set up alternative sequences that every subsequent expect command in the currently spawned process will react to in addition to the ones that you've explicitly written into the expect command. Where the returned sequence from the spawned command matches several alternatives, the order of priority taken is
>
> ~~~
> expect_before
> expect
> expect_after
> ~~~
>
> Now - one word of caution. You should specify any expect_before and expect_after commands AFTER you have spawned the process you wish to control. Expect can control multiple spawned processes, and if you specify your _befores and _afters at the wrong point, you can end up applying them to the wrong process.
>
> If you have no spawned process at all and you expect_before, you're liable to get erratic results - for example, an application which runs interactively but fails from crontab, or one which works on one release of an operating system but fails on the next release. Very hard problem to identify if you're not aware of the risk!
So the fix is really simple:
#!/usr/bin/expect -f
set timeout 3
puts "--- Start"
lassign $argv name duration
spawn bash -c "echo Hello ${name} && sleep ${duration} "
expect_before timeout { puts "\nTimeout fired"; exit 1 }
expect {
eof {
puts "No timeout";
}
}
puts "--- End"
And now it always works regardless of the -i
flag
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
Timeout fired
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论