Как обрабатывать "-" в аргументах сценария оболочки? - PullRequest
5 голосов
/ 25 мая 2011

Этот вопрос состоит из 3 частей, и каждая из них проста, но объединить их вместе - не тривиально (по крайней мере, для меня):)

Нужно написать скрипт, который должен принимать в качестве аргументов:

  1. одно имя другой команды
  2. несколько аргументов для команды
  3. список файлов

Примеры:

./my_script head -100 a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' *.txt

и т. Д.

Внутри моего скрипта по какой-то причине мне нужно различать

  • что такое команда
  • каковы аргументы для команды
  • что за файлы

так что, вероятно, самый стандартный способ написать приведенные выше примеры:

./my_script head -100 -- a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' -- *.txt

Вопрос1: Есть ли лучшее решение?

Обработка в ./my_script (первая попытка):

command="$1";shift
args=`echo $* | sed 's/--.*//'`
filenames=`echo $* | sed 's/.*--//'`

#... some additional processing ...

"$command" "$args" $filenames #execute the command with args and files

Это решение не будет работать, если filenames будет содержать spaces и / или '-', например,
/ some - путь / к / more / идиотский файл name.txt

Вопрос2: Как правильно получить $command его $args и $filenames для последующего выполнения?

Вопрос3: - как добиться следующего стиля исполнения?

echo $filenames | $command $args #but want one filename = one line (like ls -1)

Это хорошее решение для оболочки или нужно использовать, например, perl?

Ответы [ 3 ]

6 голосов
/ 25 мая 2011

Прежде всего, звучит так, как будто вы пытаетесь написать скрипт, который принимает команду и список имен файлов и запускает команду для каждого имени файла по очереди.Это можно сделать одной строкой в ​​bash:

$ for file in a.txt b.txt ./xxx/*.txt;do head -100 "$file";done
$ for file in *.txt; do sed -n 's/xxx/aaa/' "$file";done

Однако, возможно, я неправильно истолковал ваши намерения, поэтому позвольте мне ответить на ваши вопросы индивидуально.

Вместо использования "-" (который уже имеет другое значение), следующий синтаксис кажется мне более естественным:

./my_script -c "head -100" a.txt b.txt ./xxx/*.txt
./my_script -c "sed -n 's/xxx/aaa/'" *.txt

Чтобы извлечь аргументы в bash, используйте getopts:

SCRIPT=$0

while getopts "c:" opt; do
    case $opt in
        c)
            command=$OPTARG
            ;;
    esac
done

shift $((OPTIND-1))

if [ -z "$command" ] || [ -z "$*" ]; then
    echo "Usage: $SCRIPT -c <command> file [file..]"
    exit
fi

Если вы хотитечтобы выполнить команду для каждого из оставшихся аргументов, она будет выглядеть следующим образом:

for target in "$@";do
     eval $command \"$target\"
done

Если вы хотите прочитать имена файлов из STDIN, она будет выглядеть примерно так:

while read target; do
     eval $command \"$target\"
done
3 голосов
/ 26 мая 2011

Переменная $@, в кавычках будет иметь возможность группировать параметры следующим образом:

for parameter in "$@"
do
    echo "The parameter is '$parameter'"
done

Если задано:

head -100 test this "File name" out

Будет печататься

the parameter is 'head'
the parameter is '-100'
the parameter is 'test'
the parameter is 'this'
the parameter is 'File name'
the parameter is 'out'

Теперь все, что вам нужно сделать, это разобрать цикл.Вы можете использовать несколько очень простых правил:

  1. Первый параметр - это всегда имя файла
  2. Параметры, начинающиеся с тире, - это параметры
  3. После«-» или один раз не начинается с «-», остальные - имена файлов.

Вы можете проверить, является ли первый символ в параметре дефисом, используяthis:

if [[ "x${parameter}" == "x${parameter#-}" ]]

Если вы еще не видели этот синтаксис, это левый фильтр .# делит две части имени переменной.Первая часть - это имя переменной, а вторая - фильтр glob (не регулярное выражение), который нужно обрезать.В данном случае это один штрих.Пока это утверждение не соответствует действительности, вы знаете, что у вас есть параметр.Кстати, x может или не может быть необходимо в этом случае.Когда вы запускаете тест, и у вас есть строка с дефисом, тест может принять его за параметр теста, а не за значение.

Сложить это будет примерно так:

parameterFlag=""
for parameter in "$@"     #Quotes are important!
do
    if [[ "x${parameter}" == "x${parameter#-}" ]]
    then
         parameterFlag="Tripped!"
    fi
    if [[ "x${parameter}" == "x--" ]]
    then
         print "Parameter \"$parameter\" ends the parameter list"
         parameterFlag="TRIPPED!"
    fi
    if [ -n $parameterFlag ]
    then
        print "\"$parameter\" is a file"
    else
        echo "The parameter \"$parameter\" is a parameter"
    fi
done
0 голосов
/ 25 мая 2011

Вопрос 1

Я так не думаю, по крайней мере, если вам нужно сделать это для произвольных команд.

Вопрос 3

command=$1
shift
while [ $1 != '--' ]; do
    args="$args $1"
    shift
done
shift
while [ -n "$1" ]; do
    echo "$1"
    shift
done | $command $args

Вопрос 2

Чем это отличается от вопроса 3?

...