Почему я не могу использовать управление заданиями в скрипте 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.

Когда он сказал "глупо", он имел в виду, что не только:

  1. Управление заданиями предназначено главным образом для облегчения интерактивного управления (тогда как скрипт может работать непосредственно с pid), но также
  2. Я цитирую его первоначальный ответ ... полагается на тот факт, что вы ранее не выполняли никаких других заданий в сценарии, что является неверным предположением. Что совершенно правильно.

ОБНОВИТЬ

В ответ на ваш комментарий: да, никто не помешает вам использовать управление заданиями в вашем 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
Другие вопросы по тегам