Как запустить команду «grep | grep» как строку в функции bash?

Как запустить команду «grep | grep» как строку в функции bash?

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

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:

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

grep_cmd="grep -I -r $pattern $@"

if (( ${#file_types[@]} > 0 )); then
    file_types="${file_types[@]}"
    file_types=.${file_types// /':\|.'}:

    grep_cmd="$grep_cmd | grep $file_types"
fi

echo "$grep_cmd"
${grep_cmd}

Это выдает ошибку после вывода первой части:

grep: |: No such file or directory
grep: grep: No such file or directory
grep: .c:\|.h:: No such file or directory

Изменение последней строки с ${grep_cmd}на просто "$grep_cmd"не отображает вывод первой части и выдает другую ошибку:

bash: grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:: No such file or directory

Заэтот ТАК ответ, я попробовал изменить последнюю строку на $(grep_cmd). Это выдает еще одну ошибку:

bash: grep_cmd: command not found

Этот ответ ТАКпредлагает использовать eval $grep_cmd. Это подавляет ошибки, но также подавляет вывод.

Вот этотпредлагает использовать eval ${grep_cmd}. Это дает тот же результат (подавляет ошибки и вывод). Я попробовал включить отладку в bash (с set -x), что дает мне это:

+ eval grep -I -r FooBar /code/internal/dev/ /code/public/dev/ '|' grep '.c:\|.h:'
++ grep -I -r FooBar /code/internal/dev/ /code/public/dev/
++ grep '.c:|.h:'

Похоже, что канал экранируется, поэтому оболочка интерпретирует команду как две команды. Как правильно экранировать символ канала, чтобы он интерпретировал его как одну команду?

решение1

Как уже упоминалось в комментарии, большая часть ваших трудностей возникает из-за того, что вы пытаетесь сохранить команду в переменной, а затем выполнить эту команду позже.

Вам повезет гораздо больше, если вы просто выполните команду немедленно, а не будете пытаться ее сохранить.

Например, это должно сделать то, чего вы пытаетесь добиться:

if (( ${#file_types[@]} > 0 )); then
    regex="${file_types[*]}"
    regex="\.\(${regex// /\|}\):"
    grep -I -r "$pattern" "$@" | grep "$regex"
else
    grep -I -r "$pattern" "$@"
fi

решение2

При программировании оболочки следует помнить одну вещь, которая часто не объясняется четко в учебных пособиях: существует два типа данных:строки и списки строк. Список строк — это не то же самое, что строки с разделителем в виде новой строки или пробела, это нечто отдельное.

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

Некоторое расширение происходит со значением переменной: $fooозначает «взять значение переменной foo, разбить его на список строк, используя пробелы¹ в качестве разделителей, и интерпретировать каждый элемент списка как шаблон подстановочных знаков, который затем расширяется». Это расширение происходит только в том случае, если переменная используется в контексте, который требует списка. В контексте, который требует строки, $fooозначает «взять значение переменной foo». Двойные кавычки накладывают контекст строки, отсюда и совет:всегда используйте подстановки переменных и подстановки команд в двойных кавычках: "$foo","$(somecommand)"². (Те же расширения происходят с незащищенными подстановками команд, что и с переменными.)

Следствием различия между разбором и выполнением является то, что вы не можете просто вставить команду в строку и выполнить ее. Когда вы пишете ${grep_cmd}, происходит только разделение и подстановка, а не разбор, поэтому символ типа |не имеет особого значения.

Если вам абсолютно необходимо вставить команду оболочки в строку, вы можете evalэто сделать:

eval "$grep_cmd"

Обратите внимание на двойные кавычки — значение переменной содержит команду оболочки, поэтому нам нужно ее точное строковое значение. Однако этот подход, как правило, сложен: вам действительно нужно что-то в синтаксисе исходного кода оболочки. Если вам нужно, скажем, имя файла, то это имя файла должно быть правильно заключено в кавычки. Поэтому вы не можете просто вставить $patternи $@туда, вам нужно построить строку, которая при синтаксическом анализе даст одно слово, содержащее шаблон, и список слов, содержащих аргументы.

Обобщить:не вставляйте команды оболочки в переменные. Вместо,использовать функции. Если вам нужна простая команда с аргументами, а не что-то более сложное, например конвейер, вы можете использовать массив (переменная массива хранит список строк).

Вот один из возможных подходов. Функция run_grepна самом деле не нужна в коде, который вы показали; я включаю ее сюда, предполагая, что это небольшая часть более крупного скрипта и есть гораздо больше промежуточного кода. Если это действительно весь скрипт, просто запустите grep в точке, где вы знаете, куда его передать. Я также исправил код, который создает фильтр, который казался неправильным (например, .в регулярном выражении означает «любой символ», но я думаю, что вам нужна буквальная точка).

grep_cmd=(grep -I -r "$pattern" "$@")

if (( ${#file_types[@]} > 0 )); then
    regexp='\.\('
    for file_type in "${file_types[@]}"; do
      regexp="$regexp$file_type\\|"
    done
    regexp="${regexp%?}):"
    run_grep () {
      "${grep_cmd[@]}" | grep "$file_types"
    }
else
  run_grep () {
    "${grep_cmd[@]}"
  }
fi

run_grep

¹ В более общем смысле, используйте значение IFS.
² Только для экспертов: всегда используйте двойные кавычки вокруг подстановок переменных и команд, если только вы не понимаете, почему их отсутствие дает нужный эффект.
³ Только для экспертов: если вам нужно вставить команду оболочки в переменную, будьте очень осторожны с кавычками.


Обратите внимание, что то, что вы делаете, кажется слишком сложным и ненадежным — что делать, если у вас есть файл, содержащий foo.c: 42? GNU grep имеет --includeопцию для поиска только в определенных файлах в рекурсивном обходе — просто используйте ее.

grep_cmd=(grep -I -r)
for file_type in "${file_types[@]}"; do
  grep_cmd+=(--include "*.$file_type")
done
"${grep_cmd[@]}" "$pattern" "$@"

решение3

command="grep $regex1 filelist | grep $regex2"
echo $command | bash

Связанный контент