Почему я не могу использовать управление заданиями в bash script?

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

в скриптах, у вас нет управления заданиями (и попытка включить его глупо)

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

Итак, почему не работает работа по управлению заданиями на основе script, и что делает ее плохой практикой (иначе называемой "глупый" )?

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

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

Ответ 1

Что он имел в виду, так это то, что управление заданиями по умолчанию отключено в неинтерактивном режиме (т.е. в script.)

На странице bash man:

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.

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

  • - это управление заданиями, предназначенное в основном для облегчения интерактивного управления (тогда как script может работать непосредственно с pid's), но также
  • Я цитирую его оригинальный ответ,... полагается на то, что вы ранее не запускали какие-либо другие задания в script, что является плохим предположением. Что совершенно правильно.

UPDATE

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

Особые обстоятельства могут или не могут отличаться от того, что lhunath (и другие пользователи) считают "лучшими практиками".

Ответ 2

Управление заданиями с помощью bg и fg полезно только в интерактивных оболочках. Но & в сочетании с wait также полезен в скриптах.

В многопроцессорных системах нерестовые задания фона могут значительно улучшить производительность script, например. в сценариях сборки, где вы хотите запустить хотя бы один компилятор на один процессор, или обрабатывать изображения с помощью инструментов 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, которая ждет всех фоновых заданий до script. PID последнего задания фона хранится в переменной $!, поэтому вы также можете wait ${!}. Также обратите внимание на команду nice.

Иногда такой код полезен в make файлах:

buildall:
    for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait

Это дает намного более тонкое управление, чем make -j.

Обратите внимание, что & является терминатором строки, подобным ; (напишите command& not command&;).

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

Ответ 3

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

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

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

Ответ 4

Может быть полезно включить управление заданиями в script, чтобы установить ловушки на SIGCHLD. Раздел JOB CONTROL в руководстве говорит:

Оболочка сразу узнает, когда изменяется состояние задания. Как обычно, bash ожидает, пока он собирается распечатать приглашение перед отчетностью изменения в статусе задания, чтобы не прерывать какой-либо другой выход. Если опция -b для установленной встроенной команды включена, bash отчетов такие изменения немедленно. Любая ловушка в SIGCHLD выполняется для каждого дочерний элемент, который завершает работу.

(акцент мой)

В качестве примера возьмем script:

[email protected]:~$ 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
[email protected]:~$ chmod +x children.bash 
[email protected]:~$ ./children.bash 
sleeping 6 seconds
sleeping 0 seconds
sleeping 7 seconds

Примечание: Улавливание CHLD, по-видимому, нарушено как bash 4.3

В bash 4.3 вы можете использовать "wait -n" для достижения того же, хотя:

[email protected]:~$ 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
[email protected]:~$ chmod +x waitn.bash 
[email protected]:~$ ./waitn.bash 
sleeping 3 seconds
sleeping 0 seconds
sleeping 5 seconds

Вы можете утверждать, что есть другие способы сделать это в более переносимый путь, то есть без CHLD или wait -n:

[email protected]:~$ 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
[email protected]:~$ chmod +x portable.sh 
[email protected]:~$ ./portable.sh 
sleeping 2 seconds
sleeping 5 seconds
sleeping 6 seconds

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

Ответ 5

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

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

#!/bin/sh

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

#!/bin/bash

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

Ответ 6

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

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

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

kill -STOP pid

Я довольно часто это делаю, если хочу приостановить текущую работу sudo, как в:

kill -STOP $$

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

Я имею тенденцию использовать мнемонический -KILL и т.д., потому что существует опасность ввода

kill - 9 pid # note the space

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

Ответ 7

задания DO работают в bash скриптах

НО, вам... НУЖНО следить за созданным персоналом как:

ls -1 /usr/share/doc/ | while read -r doc ; do ... done

задания будут иметь разный контекст с каждой стороны |

обход этого может использоваться вместо:

for `ls -1 /usr/share/doc` ; do ... done

это должно продемонстрировать, как использовать задания в script... с упоминанием о том, что мое примечание примечание... REAL (не знаю, почему это поведение)

    #!/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