Почему я не могу использовать управление заданиями в скрипте bash? - PullRequest
47 голосов
/ 27 марта 2009

В этот ответ на другой вопрос , мне сказали, что

в сценариях у вас нет контроля работы (и пытаться включить это глупо)

Это первый раз, когда я слышал это, и я изучил раздел bash.info по управлению заданиями (глава 7), не найдя упоминания ни об одном из этих утверждений. [ Обновление: Страница справочника немного лучше, с упоминанием «типичного» использования, настроек по умолчанию и терминального ввода-вывода, но без реальной причины, почему управление заданиями особенно плохо рекомендуется для сценариев.]

Так почему же не работает управление заданиями на основе сценариев и что делает его плохой практикой (он же «глупый»)?

Редактировать: Рассматриваемый скрипт запускает фоновый процесс, запускает второй фоновый процесс, затем пытается вернуть первый процесс на передний план так, чтобы он имел нормальный терминальный ввод-вывод (как если бы он выполнялся напрямую), который затем может быть перенаправлен извне скрипта . Не могу сделать это с фоновым процессом.

Как отмечено в принятом ответе на другой вопрос, существуют другие сценарии, которые решают эту конкретную проблему, не пытаясь контролировать работу. Хорошо. И в скриптовом сценарии используется жестко заданный номер задания & mdash; Очевидно, плохо. Но я пытаюсь понять, является ли контроль за работой принципиально обреченным подходом. Все еще кажется, что, может быть, это может работать ...

Ответы [ 7 ]

42 голосов
/ 27 марта 2009

Он имел в виду, что управление заданиями по умолчанию отключено в неинтерактивном режиме (т. Е. В скрипте.)

со страницы руководства 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. Я цитирую его первоначальный ответ, ... опирается на тот факт, что вы ранее не выполняли никаких других заданий в сценарии, что является неверным предположением для . Что совершенно правильно.

UPDATE

В ответ на ваш комментарий: да, никто не помешает вам использовать управление заданиями в вашем bash-скрипте - нет особого случая для принудительного отключения set -m (т.е. да, управление заданиями из Скрипт будет работать, если вы этого хотите.) Помните, что в конце концов, особенно в скриптах, всегда есть более чем один способ скинуть кошку, но некоторые способы более переносимы, более надежны, упрощают обработку ошибок, разбирать вывод и т. д.

Вы в определенных обстоятельствах можете или не можете оправдать действия, отличные от того, что lhunath (и другие пользователи) считают «наилучшей практикой».

29 голосов
/ 02 октября 2012

Управление заданиями с 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&;).

Надеюсь, это поможет.

6 голосов
/ 27 марта 2009

Управление заданиями полезно только тогда, когда вы используете интерактивную оболочку, т.е. вы знаете, что stdin и stdout подключены к терминальному устройству (/ dev / pts / * в Linux). Тогда имеет смысл иметь что-то на переднем плане, что-то еще на заднем плане и т. Д.

Сценарии, с другой стороны, не имеют такой гарантии. Скрипты можно сделать исполняемыми и запускать без присоединенного терминала. В этом случае нет смысла иметь передний или фоновый процессы.

Однако вы можете запускать другие команды неинтерактивно в фоновом режиме (добавляя «&» в командную строку) и записывать их PID с помощью $!. Затем вы используете kill, чтобы убить или приостановить их (имитируя Ctrl-C или Ctrl-Z на терминале, если оболочка была интерактивной). Вы также можете использовать wait (вместо fg), чтобы дождаться завершения фонового процесса.

5 голосов
/ 15 апреля 2014

Может быть полезно включить управление заданиями в скрипте для установки ловушек 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

Итак, в заключение установите -m , а не , что полезно в сценариях, так как единственная интересная особенность, которую он приносит в сценарии, это возможность работать с SIGCHLD. И есть другие способы добиться того же либо более короткий (wait -n), либо более переносимый (отправка сигналов самостоятельно).

2 голосов
/ 27 марта 2009

Bash поддерживает управление заданиями, как вы говорите. При написании сценариев оболочки часто существует предположение, что вы не можете полагаться на тот факт, что у вас есть bash, но у вас есть ванильная оболочка Bourne (sh), которая исторически не имела контроля работы.

В наши дни мне трудно представить систему, в которой вы честно ограничены настоящей оболочкой Борна. Большинство систем /bin/sh будут связаны с bash. Тем не менее, это возможно. Одна вещь, которую вы можете сделать, это вместо указания

#!/bin/sh

Вы можете сделать:

#!/bin/bash

Это и ваша документация прояснят необходимость вашего скрипта bash.

0 голосов
/ 24 мая 2012

заданий работают в 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
0 голосов
/ 29 марта 2009

Возможно, o / t, но я довольно часто использую nohup, когда ssh подключается к серверу на долгосрочном задании, поэтому, если я выйду из системы, задание все равно завершится.

Интересно, не сбивают ли людей с толку остановки и запуска с мастер-интерактивной оболочки и порождения фоновых процессов? Команда wait позволяет вам порождать много вещей, а затем ждать, пока все они завершатся, и, как я уже говорил, я все время использую nohup. Это сложнее, чем это, и очень мало используется - sh поддерживает этот режим тоже. Посмотрите руководство.

У вас также есть

kill -STOP pid

Я довольно часто так делаю, если хочу приостановить запущенный в данный момент sudo, например:

kill -STOP $$

Но горе вам, если вы выпрыгнули в оболочку из редактора - все это будет просто сидеть.

Я склонен использовать мнемонику -KILL и т. Д., Поскольку существует опасность ввода

kill - 9 pid # note the space

и в старые времена вы могли иногда останавливать машину, потому что она убивала init!

...