TL, DR:
array_of_lines=("${(@f)$(my_command)}")
Первая ошибка (→ Q2): IFS='\n'
наборы IFS
к этим двум символам \
и n
. Установить IFS
к новой строке использовать IFS=$'\n'
.
Вторая ошибка: для установки переменной на значение массива Вам нужны круглые скобки вокруг элементов: array_of_lines=(foo bar)
.
Это работало бы, за исключением того, что это разделяет пустые строки, потому что последовательный пробел рассчитывает как единственный разделитель:
IFS=$'\n' array_of_lines=($(my_command))
Можно сохранить пустые строки кроме в самом конце путем удвоения пробельного символа в IFS
:
IFS=$'\n\n' array_of_lines=($(my_command))
Чтобы продолжать запаздывать пустые строки также, необходимо было бы добавить что-то к выводу команды, потому что это происходит в самой замене команды, не от парсинга его.
IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'
(принятие вывода my_command
не заканчивается в неразграниченной строке; также обратите внимание потерю статуса выхода my_command
)
Обратите внимание что все отрывки выше отпуска IFS
с его значением не по умолчанию, таким образом, они могут испортить последующий код. Сохранять установку IFS
локальный, помещает все это в функцию, где Вы объявляете IFS
локальный (здесь также заботящийся о сохранении статуса выхода команды):
collect_lines() {
local IFS=$'\n\n' ret
array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
ret=$?
unset 'array_of_lines[-1]'
return $ret
}
collect_lines my_command
Но я рекомендую не смешать с IFS
; вместо этого, используйте f
флаг расширения для разделения на новых строках (→ Q1):
array_of_lines=("${(@f)$(my_command)}")
Или сохранить запаздывание пустых строк:
array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'
Значение IFS
не имеет значения там. Я подозреваю, что Вы использовали команду, которая разделяет на IFS
распечатать $array_of_lines
в Ваших тестах (→ Q3).
Ключи сигнала, такие как Ctrl+C отправляют сигнал во все процессы в группе приоритетного процесса.
В типичном случае группа процесса является конвейером. Например, в head <somefile | sort
, выполнение процесса head
и выполнение процесса sort
находятся в той же группе процесса, как оболочка, таким образом, они все получают сигнал. Когда Вы выполняете задание в фоновом режиме (somecommand &
), то задание находится в своей собственной группе процесса, так нажатие Ctrl+C не влияет на него.
timeout
программа ставит себя в свою собственную группу процесса. От исходного кода:
/* Ensure we're in our own group so all subprocesses can be killed.
Note we don't just put the child in a separate group as
then we would need to worry about foreground and background groups
and propagating signals between them. */
setpgid (0, 0);
Когда тайм-аут происходит, timeout
проходит простое целесообразное из уничтожения, группой процесса которого это - участник. Так как это поместило себя в отдельную группу процесса, ее родительский процесс не будет в группе. Используя группу процесса здесь гарантирует это, если дочерние ветвления приложения в несколько процессов, все его процессы получат сигнал.
Когда Вы работаете timeout
непосредственно на командной строке и нажимают Ctrl+C, получающийся SIGINT получен оба timeout
и дочерним процессом, но не интерактивной оболочкой, которая является timeout
родительский процесс. Когда timeout
назван из сценария, только оболочка, запускающая скрипт, получает сигнал: timeout
не получает его, так как это находится в другой группе процесса.
Можно установить обработчик сигналов в сценарии оболочки с trap
встроенный. К сожалению, дело не в этом простой. Рассмотрите это:
#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date
При нажатии Ctrl+C после 2 секунд это все еще ожидает целые 5 секунд, то распечатайте “Прерванное” сообщение. Поэтому оболочка воздерживается от выполнения кода прерывания, в то время как задание переднего плана активно.
Для исправления этого выполните задание в фоновом режиме. В обработчике сигналов звонить kill
передавать сигнал к timeout
группа процесса.
#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid
В основном Ctrl+C отправляет a SIGINT
сигнал, в то время как Ctrl+Z отправляют a SIGTSTP
сигнал.
SIGTSTP
просто останавливает процесс, a SIGCONT
продолжит его.
Это работает над приоритетным процессом, который был разветвлен на командной строке.
Если Ваш процесс является фоновым процессом, необходимо будет отправить тот сигнал в процессы по-другому. kill
сделает это. В теории a "-" - оператор, на котором уничтожение должно также сигнализировать о дочерних процессах, но это редко работает как ожидалось.
Для дополнительных материалов для чтения: сигналы Unix
Основываясь на отличном ответе Жиля. У команды timeout есть опция foreground, если она используется, то нажатие CTRL+C приведет к выходу из команды timeout.
#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date
Улучшение ответа @Gilles путем предоставления подхода «найти и заменить» для существующих скриптов:
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/a/1446738/187576
my_timeout(){
local args tp ret
args="$@"
timeout $args &
tp=$!
echo "pid of timeout: $tp"
timeout_pids+=($tp)
wait $tp
ret=$?
count=${#timeout_pids[@]}
for ((i = 0; i < count; i++)); do
if [ "${timeout_pids[i]}" = "$tp" ] ; then
unset 'timeout_pids[i]'
fi
done
return $ret
}
INT
:pre_cleanup(){
exec 1>&21; exec 2>&22 # restore file descriptors, see https://superuser.com/a/1446738/187576
echo "Executing pre-cleanup..."
for i in "${timeout_pids[*]}"; do
if [[ ! -z $i ]]; then
#echo "Killing PID: $i"
kill -INT -$i 2> /dev/null
fi
done
exit
}
trap pre_cleanup INT
timeout
на функцию my_timeout
в вашем скрипте. Вот пример скрипта:
#!/bin/bash
# see "killing timeout": https://unix.stackexchange.com/a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/a/1446738/187576
my_timeout(){
local args tp ret
args="$@"
timeout $args &
tp=$!
echo "pid of timeout: $tp"
timeout_pids+=($tp)
wait $tp
ret=$?
count=${#timeout_pids[@]}
for ((i = 0; i < count; i++)); do
if [ "${timeout_pids[i]}" = "$tp" ] ; then
unset 'timeout_pids[i]'
fi
done
return $ret
}
cleanup(){
echo "-----------------------------------------"
echo "Restoring previous routing table settings"
}
pre_cleanup(){
exec 1>&21; exec 2>&22 # restore file descriptors, see https://superuser.com/a/1446738/187576
echo "Executing pre-cleanup..."
for i in "${timeout_pids[*]}"; do
if [[ ! -z $i ]]; then
echo "Killing PID: $i"
kill -INT -$i 2> /dev/null
fi
done
exit
}
trap pre_cleanup INT
trap cleanup EXIT
echo "Executing 5 temporary timeouts..."
unreachable_ip="192.168.44.5"
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
echo "ctrl+c now to execute cleanup"
my_timeout 9s ping -c 1 "$unreachable_ip" &> /dev/null