Я не могу понять, почему timeout
вызов функции приведет к остановке цикла. У меня есть "решение", но я действительно очень заинтригован тем, как / почему это происходит! Кажется, это как-то связано с тем, cat
что команда истекает по тайм-ауту?
TL;DR
while read -r line; do ... done < file
завершается, когда timeout
происходит на cat
, производя неправильный вывод и код выхода. Цикл делаетнетпройтись по каждой строке файла.
Если вместо этого сначала создать массив всех строк в файле, а затем ...
выполнить его в for line in "${all_lines[@]}"; do
, все строки будут обработаны, и вывод timeout
с точки зрения кодов завершения будет правильным.
Предположим, что скрипт grade.sh
намеревается прочитать все tests.txt
и выполнить soln.sh
, убедившись, что soln.sh
завершается. Чтобы продемонстрировать "рабочий" пример, soln.sh
сначала sleep
.
tests.txt
first
second
third
fourth
fifth
grade.sh
#!/usr/bin/env bash
while read -r line; do
echo "Test: $line"
output="$(timeout 2 ./soln.sh "$line")"
timed_exit=$?
echo " Soln Output: $output"
echo " Timed exit: $timed_exit"
done < "tests.txt"
soln.sh
#!/usr/bin/env bash
if [[ "$1" == "third" ]]; then
sleep 3
fi
echo "[soln running $1]"
ожидаемый результат
Test: first
Soln Output: [soln running first]
Timed exit: 0
Test: second
Soln Output: [soln running second]
Timed exit: 0
Test: third
Soln Output:
Timed exit: 124
Test: fourth
Soln Output: [soln running fourth]
Timed exit: 0
Test: fifth
Soln Output: [soln running fifth]
Timed exit: 0
Если мы изменим soln
действие, чтобы оно продолжалось вечно (ожидание ввода), цикл завершится.
soln.sh
#!/usr/bin/env bash
if [[ "$1" == "third" ]]; then
cat $(find . -name iamnothere.txt) | wc -l
fi
echo "[soln running $1]"
вывод завершается преждевременно, лишний 2
, неправильный exit
код
Test: first
Soln Output: [soln running first]
Timed exit: 0
Test: second
Soln Output: [soln running second]
Timed exit: 0
Test: third
Soln Output: 2
[soln running third]
Timed exit: 0
Хакерское решение — сначала пройтись по каждой строке и использовать for
цикл, который обойдет это.
"зафиксированный"grade.sh
#!/usr/bin/env bash
all_lines=()
idx=0
while read -r line; do
all_lines[idx]="$line"
(( idx++ ))
done < "tests.txt"
for line in "${all_lines[@]}"; do
echo "Test: $line"
output="$(timeout 2 ./soln.sh "$line")"
timed_exit=$?
echo " Soln Output: $output"
echo " Timed exit: $timed_exit"
done
ожидаемый результат
Test: first
Soln Output: [soln running first]
Timed exit: 0
Test: second
Soln Output: [soln running second]
Timed exit: 0
Test: third
Soln Output:
Timed exit: 124
Test: fourth
Soln Output: [soln running fourth]
Timed exit: 0
Test: fifth
Soln Output: [soln running fifth]
Timed exit: 0
Это фича, баг или я что-то упускаю?
Мне кажется, что cat
это как-то переопределяет timeout
, поскольку остальная часть скрипта может быть выполнена.
решение1
cat $(find . -name iamnothere.txt) | wc -l
предполагая, что iamnothere.txt
не существует, становится
cat | wc -l
который потребляет стандартный ввод,тот же стандартный ввод, while
из которого цикл считывает строки. for
избегает этого, не используя стандартный ввод, как while
делает. Это можно наблюдать, используя bare cat
для случая второй строки, поскольку это показывает, что третья строка была прочитана этим cat
:
$ cat lines
first
secon
third
$ cat looper
#!/bin/sh
while read line; do
x=$(timeout 2 ./doer "$line")
echo "$line out=$x code=$?"
done < lines
$ cat doer
#!/bin/sh
if [ "$1" = secon ]; then
cat
else
echo "$1 pid$$"
fi
$ ./looper
first out=first pid42079 code=0
secon out=third code=0
$