Почему я не могу использовать управление заданиями в скрипте bash?
В этом ответе на другой вопрос мне сказали, что
в сценариях у вас нет контроля работы (и пытаться включить его глупо)
Это первый раз, когда я слышал это, и я изучил раздел bash.info по управлению заданиями (глава 7), не обнаружив ни одного из этих утверждений. [ Обновление: страница руководства немного лучше, в ней упоминается "типичное" использование, настройки по умолчанию и терминальный ввод-вывод, но нет реальной причины, по которой управление заданиями особенно плохо рекомендуется для сценариев.]
Так почему же не работает управление заданиями на основе сценариев и что делает его плохой практикой ("глупый")?
Редактировать: рассматриваемый скрипт запускает фоновый процесс, запускает второй фоновый процесс, затем пытается поместить первый процесс обратно на передний план, чтобы он имел обычный терминальный ввод-вывод (как если бы он выполнялся напрямую), который затем можно перенаправить из вне сценария. Не могу сделать это с фоновым процессом.
Как отмечено в принятом ответе на другой вопрос, существуют другие сценарии, которые решают эту конкретную проблему, не пытаясь контролировать работу. Хорошо. И в скриптовом сценарии используется жестко запрограммированный номер задания - очевидно, плохой. Но я пытаюсь понять, является ли контроль за работой принципиально обреченным подходом. Кажется, все еще может сработать...
7 ответов
Он имел в виду, что управление заданиями по умолчанию отключено в неинтерактивном режиме (т.е. в сценарии).
От bash
справочная страница:
JOB CONTROL
Job control refers to the ability to selectively stop (suspend)
the execution of processes and continue (resume) their execution at a
later point.
A user typically employs this facility via an interactive interface
supplied jointly by the system’s terminal driver and bash.
а также
set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
...
-m Monitor mode. Job control is enabled. This option is on by
default for interactive shells on systems that support it (see
JOB CONTROL above). Background processes run in a separate
process group and a line containing their exit status is
printed upon their completion.
Когда он сказал "глупо", он имел в виду, что не только:
- Управление заданиями предназначено главным образом для облегчения интерактивного управления (тогда как скрипт может работать непосредственно с pid), но также
- Я цитирую его первоначальный ответ ... полагается на тот факт, что вы ранее не выполняли никаких других заданий в сценарии, что является неверным предположением. Что совершенно правильно.
ОБНОВИТЬ
В ответ на ваш комментарий: да, никто не помешает вам использовать управление заданиями в вашем bash-скрипте - для насильственного отключения принудительного отключения не существует set -m
(т. е. да, управление заданиями из сценария будет работать, если вы этого хотите.) Помните, что в конце, особенно в сценариях, всегда есть более чем один способ снять шкуру с кошки, но некоторые способы являются более переносимыми, более надежными, упростить обработку ошибок, анализ выходных данных и т. д.
Вы, конкретные обстоятельства, можете или не можете оправдать путь, отличный от того, что lhunath
(и другие пользователи) считают "лучшие практики".
Контроль работы с bg
а также fg
полезен только в интерактивных оболочках. Но &
в сочетании с wait
тоже полезно в скриптах.
В многопроцессорных системах порождение фоновых заданий может значительно повысить производительность сценария, например, в сценариях сборки, где вы хотите запустить хотя бы один компилятор на процессор, или параллельно обрабатывать изображения с помощью инструментов ImageMagick и т. Д.
В следующем примере выполняется до 8 параллельных gcc для компиляции всех исходных файлов в массиве:
#!bash
...
for ((i = 0, end=${#sourcefiles[@]}; i < end;)); do
for ((cpu_num = 0; cpu_num < 8; cpu_num++, i++)); do
if ((i < end)); then gcc ${sourcefiles[$i]} & fi
done
wait
done
В этом нет ничего "глупого". Но вам потребуется wait
команда, которая ждет всех фоновых заданий, прежде чем скрипт продолжится. PID последнего фонового задания сохраняется в $!
переменная, так что вы также можете wait ${!}
, Обратите внимание также на nice
команда.
Иногда такой код полезен в make-файлах:
buildall:
for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait
Это дает гораздо более тонкий контроль, чем make -j
,
Обратите внимание, что &
терминатор строки, как ;
(записывать command&
не command&;
).
Надеюсь это поможет.
Управление заданиями полезно только тогда, когда вы используете интерактивную оболочку, т.е. вы знаете, что stdin и stdout подключены к терминальному устройству (/dev/pts/* в Linux). Тогда имеет смысл иметь что-то на переднем плане, что-то еще на заднем плане и т. Д.
Сценарии, с другой стороны, не имеют такой гарантии. Скрипты можно сделать исполняемыми и запускать без присоединенного терминала. В этом случае нет смысла иметь передний или фоновый процессы.
Однако вы можете запускать другие команды неинтерактивно в фоновом режиме (добавляя "&" к командной строке) и записывать их PID с помощью $!
, Тогда вы используете kill
убить или приостановить их (имитируя Ctrl-C или Ctrl-Z на терминале, если оболочка была интерактивной). Вы также можете использовать wait
(вместо fg
) дождаться окончания фонового процесса.
Может быть полезно включить управление заданиями в сценарии для установки ловушек на SIGCHLD. Раздел "УПРАВЛЕНИЕ РАБОТОЙ" в руководстве гласит:
Оболочка обучается сразу же, когда задание меняет состояние. Обычно bash ждет, пока он собирается напечатать приглашение, прежде чем сообщать об изменениях в статусе задания, чтобы не прерывать любой другой вывод. Если опция -b для встроенной команды set включена, bash немедленно сообщает о таких изменениях. Любая ловушка на SIGCHLD выполняется для каждого выходящего ребенка.
(акцент мой)
Возьмите следующий скрипт, как пример:
dualbus@debian:~$ cat children.bash
#!/bin/bash
set -m
count=0 limit=3
trap 'counter && { job & }' CHLD
job() {
local amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
}
counter() {
((count++ < limit))
}
counter && { job & }
wait
dualbus@debian:~$ chmod +x children.bash
dualbus@debian:~$ ./children.bash
sleeping 6 seconds
sleeping 0 seconds
sleeping 7 seconds
Примечание: перехват CHLD, по-видимому, нарушен с bash 4.3
В bash 4.3 вы можете использовать wait -n для достижения того же результата:
dualbus@debian:~$ cat waitn.bash
#!/home/dualbus/local/bin/bash
count=0 limit=3
trap 'kill "$pid"; exit' INT
job() {
local amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
}
for ((i=0; i<limit; i++)); do
((i>0)) && wait -n; job & pid=$!
done
dualbus@debian:~$ chmod +x waitn.bash
dualbus@debian:~$ ./waitn.bash
sleeping 3 seconds
sleeping 0 seconds
sleeping 5 seconds
Вы можете утверждать, что есть другие способы сделать это более переносимым способом, то есть без CHLD или wait -n:
dualbus@debian:~$ cat portable.sh
#!/bin/sh
count=0 limit=3
trap 'counter && { brand; job & }; wait' USR1
unset RANDOM; rseed=123459876$$
brand() {
[ "$rseed" -eq 0 ] && rseed=123459876
h=$((rseed / 127773))
l=$((rseed % 127773))
rseed=$((16807 * l - 2836 * h))
RANDOM=$((rseed & 32767))
}
job() {
amount=$((RANDOM % 8))
echo "sleeping $amount seconds"
sleep "$amount"
kill -USR1 "$$"
}
counter() {
[ "$count" -lt "$limit" ]; ret=$?
count=$((count+1))
return "$ret"
}
counter && { brand; job & }
wait
dualbus@debian:~$ chmod +x portable.sh
dualbus@debian:~$ ./portable.sh
sleeping 2 seconds
sleeping 5 seconds
sleeping 6 seconds
Итак, в заключение, set -m не так полезен в сценариях, поскольку единственная интересная особенность, которую он дает сценариям, - это возможность работать с SIGCHLD. Есть и другие способы достижения того же результата: более короткий (wait -n) или более переносимый (отправка сигналов самостоятельно).
Bash поддерживает контроль работы, как вы говорите. При написании сценариев оболочки часто существует предположение, что вы не можете полагаться на тот факт, что у вас есть bash, но у вас есть ванильная оболочка Bourne (sh
), который исторически не имел контроля за работой.
В эти дни мне трудно представить систему, в которой вы честно ограничены настоящей оболочкой Борна. Большинство систем /bin/sh
будет связан с bash
, Тем не менее, это возможно. Одна вещь, которую вы можете сделать, это вместо указания
#!/bin/sh
Ты можешь сделать:
#!/bin/bash
Это и ваша документация прояснят потребности вашего скрипта bash
,
Возможно, o / t, но я довольно часто использую nohup, когда ssh подключается к серверу на долго выполняемой работе, так что, если я выйду из системы, работа все равно завершится.
Интересно, не сбивают ли людей с толку остановки и запуска с мастер-интерактивной оболочки и порождения фоновых процессов? Команда wait позволяет вам порождать много вещей, а затем ждать их завершения, и, как я уже сказал, я все время использую nohup. Это сложнее, чем это и очень недооценено - sh поддерживает этот режим тоже. Посмотрите на руководство.
Вы также получили
kill -STOP pid
Я часто делаю это, если хочу приостановить запущенный в данный момент sudo, как в:
kill -STOP $$
Но горе вам, если вы выпрыгнули в оболочку из редактора - все будет просто сидеть там.
Я склонен использовать мнемонический -KILL и т. Д., Потому что есть опасность ввода
kill - 9 pid # note the space
и в старые времена вы могли иногда останавливать машину, потому что это убивало init!
Рабочие места работают в скриптах bash
НО, вам... НУЖНО следить за порожденным персоналом, как:
ls -1 /usr/share/doc/ | while read -r doc ; do ... done
вакансии будут иметь различный контекст на каждой стороне |
в обход этого можно использовать вместо вместо:
for `ls -1 /usr/share/doc` ; do ... done
это должно продемонстрировать, как использовать задания в скрипте... с упоминанием, что моя закомментированная заметка... РЕАЛЬНА (не знаю, почему это поведение)
#!/bin/bash
for i in `seq 7` ; do ( sleep 100 ) & done
jobs
while [ `jobs | wc -l` -ne 0 ] ; do
for jobnr in `jobs | awk '{print $1}' | cut -d\[ -f2- |cut -d\] -f1` ; do
kill %$jobnr
done
#this is REALLY ODD ... but while won't exit without this ... dunno why
jobs >/dev/null 2>/dev/null
done
sleep 1
jobs