Передача нескольких аргументов в сценарий оболочки UNIX - PullRequest
3 голосов
/ 24 декабря 2010

У меня есть следующий (bash) сценарий оболочки, который в идеале я бы использовал для уничтожения нескольких процессов по имени.

#!/bin/bash
kill `ps -A | grep $* | awk '{ print $1 }'`

Однако, пока этот скрипт работает, передается один аргумент:

конец хром

(имя скрипта заканчивается)

не работает, если передано более одного аргумента:

$ end chrome firefox

grep: firefox: Нет такого файла или каталога

Что здесь происходит?

Я думал, что $* передает несколько аргументов скрипту оболочки последовательно. Я ничего не ошибаюсь в своих данных - и программы, которые я хочу убить (chrome и firefox), открыты.

Любая помощь приветствуется.

Ответы [ 4 ]

3 голосов
/ 25 декабря 2010

Помните, что grep делает с несколькими аргументами - первое - это слово для поиска, а остальные - файлы для сканирования.

Также помните, что $*, "$*" и $@ все теряют след пустого пространства в аргументах, в то время как магические обозначения "$@" не делают.

Итак, чтобы разобраться с вашим делом, вам нужно изменить способ вызова grep,Вам либо нужно использовать grep -F (он же fgrep) с опциями для каждого аргумента, либо вам нужно использовать grep -E (он же egrep) с чередованием.Частично это зависит от того, придется ли вам иметь дело с аргументами, которые сами содержат символы конвейера.

Удивительно сложно сделать это надежно с помощью одного вызова grep;Вы, возможно, лучше всего допустите накладные расходы на запуск конвейера несколько раз:

for process in "$@"
do
    kill $(ps -A | grep -w "$process" | awk '{print $1}')
done

Если накладные расходы на запуск ps несколько раз, как это слишком больно (мне больно писать это - но явы не измерили стоимость), то вы, вероятно, делаете что-то вроде:

case $# in
(0) echo "Usage: $(basename $0 .sh) procname [...]" >&2; exit 1;;
(1) kill $(ps -A | grep -w "$1" | awk '{print $1}');;
(*) tmp=${TMPDIR:-/tmp}/end.$$
    trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15
    ps -A > $tmp.1
    for process in "$@"
    do
         grep "$process" $tmp.1
    done |
    awk '{print $1}' |
    sort -u |
    xargs kill
    rm -f $tmp.1
    trap 0
    ;;
esac

Использование простого xargs нормально, поскольку оно имеет дело со списком идентификаторов процессов, а идентификаторы процессов не содержатпробелы или переводы строки.Это сохраняет простой код для простого случая;сложный случай использует временный файл для хранения вывода ps, а затем сканирует его один раз для каждого имени процесса в командной строке.sort -u гарантирует, что если какой-либо процесс будет соответствовать всем вашим ключевым словам (например, grep -E '(firefox|chrome)' будет соответствовать обоим), будет отправлен только один сигнал.

Строки прерываний и т. Д. Гарантируют, что временный файл очищендо тех пор, пока кто-то не будет чрезмерно жесток к команде (пойманы следующие сигналы: HUP, INT, QUIT, PIPE и TERM, aka 1, 2, 3, 13 и 15; ноль ловит оболочку, выходящую по любой причине).Каждый раз, когда сценарий создает временный файл, у вас должна быть похожая ловушка вокруг использования файла, чтобы он был очищен, если процесс завершен.

Если вы чувствуете себя осторожно и у вас есть GNU Grep, вы можете добавить опцию -w, чтобы имена, указанные в командной строке, соответствовали только целым словам.


Все вышеперечисленное будет работать практически с любой оболочкой в ​​Bourne / Korn / POSIX / Bashсемейство (вам нужно будет использовать обратные метки со строгой оболочкой Борна вместо $(...), а ведущие скобки в условиях в case также не допускаются с оболочкой Борна).Тем не менее, вы можете использовать массив для правильной обработки.

n=0
unset args  # Force args to be an empty array (it could be an env var on entry)
for i in "$@"
do
    args[$((n++))]="-e"
    args[$((n++))]="$i"
done
kill $(ps -A | fgrep "${args[@]}" | awk '{print $1}')

Это тщательно сохраняет интервалы в аргументах и ​​использует точные совпадения для имен процессов.Избегает временных файлов.Показанный код не проверяется на отсутствие аргументов;это должно быть сделано заранее.Или вы можете добавить строку args[0]='/collywobbles/' или что-то подобное, чтобы предоставить команду по умолчанию - несуществующую - для поиска.

2 голосов
/ 24 декабря 2010

Чтобы ответить на ваш вопрос, $* расширяется до списка параметров, поэтому второе и последующие слова выглядят как файлы в grep(1).

Чтобы обработать их последовательно, вы должны сделать что-то вроде:

for i in $*; do
    echo $i
done

Как правило, "$@" (с кавычками) используется вместо $* в подобных случаях.

См. man sh, а также проверьте killall(1), pkill(1) и pgrep(1).

0 голосов
/ 24 декабря 2010

$* следует использовать редко. Я бы вообще рекомендовал "$@". Разбор аргументов в оболочке относительно сложен, и его легко ошибиться. Как правило, вы понимаете, что это неправильно, и в конечном итоге оценивают вещи, которых не должно быть.

Например, если вы ввели это:

end '`rm foo`'

вы обнаружите, что если бы у вас был файл с именем 'foo', его больше нет.

Вот скрипт, который будет делать то, что вы просили сделать. Сбой, если любой из аргументов содержит '\n' или '\0' символов:

#!/bin/sh

kill $(ps -A | fgrep -e "$(for arg in "$@"; do echo "$arg"; done)" | awk '{ print $1; }')

Я предпочитаю синтаксис $(...) делать то, что делает backtick. Это намного яснее, и это также менее двусмысленно, когда вы вкладываете вещи.

0 голосов
/ 24 декабря 2010

Просмотрите pkill(1) или killall(1) как комментарии @khachik.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...