У меня есть существующий скрипт bash, который я вызываю следующим образом:

find /path/to/my/stuff -type d -exec sh -c 'cd "$0"; /path/to/my/script.sh function_name fn_parameter' {} \;

Я часто меняю /path/to/my/stuff и fn_parameter. Мне также нужно иногда менять function_name.

Постоянно вводить эту команду становится утомительно, поэтому я хотел бы обернуть ее в другой скрипт и передать только эти три параметра, например:

wrapper.sh function_name "/path/to/my/stuff" fn_parameter

Примечание: я изменил порядок аргументов, потому что «имя_функции» меняется реже всего.

Я перегружен цитированием и экранированием, когда пытаюсь создать этот скрипт-оболочку. Я просмотрел сценарий на shellcheck.net, а также безуспешно пытался использовать массив для cmd (см. одну из моих неудачных попыток ниже). Я понимаю, что проблема, скорее всего, в том, что кавычки и обратные косые черты не соблюдаются, но я не могу понять, как это решить.

Это одна из моих многих неудачных попыток:

Wrapper.sh

#!/bin/bash
function_name=$1
mpath="$2"
arg=$3

find "$mpath" -type d -exec sh -c "cd \"$0\"; /path/to/my/script.sh $function_name $arg" {} \;

Вот еще один пример неудачной попытки:

#!/bin/bash
function_name=$1
mpath="$2"
arg=$3
cmd="'cd \$0; /path/to/my/script.sh $function_name $arg'"
echo "find \"$mpath\" -type d -exec sh -c $cmd {} \;"
find "$mpath" -type d -exec sh -c $cmd {} \;

В приведенном выше примере, если я ввожу вывод оператора echo в командную строку, он работает правильно. Но оболочка терпит неудачу с:

$0;: -c: line 1: unexpected EOF while looking for matching `''
$0;: -c: line 2: syntax error: unexpected end of file

Для полноты myscript.sh похож на это:

#!/bin/bash

fn1() {
  ...
}

fn2() {
  ...
}


fn3() {
  ...
}

"$@"
0
MountainX 20 Сен 2021 в 00:12
$0 — имя текущего скрипта. Кажется неправильным пытаться использовать cd для него... В любом случае, чтобы попытаться упростить этот код, я бы разделил find (для перечисления всех аргументов, с которыми нужно действовать) и команду для выполнения с этими аргументами. Нет необходимости принудительно выполнять оба действия с помощью find. Вы можете использовать xargs и/или find ... | while read arg; do command somefunc $arg; done
 – 
arielf
19 Сен 2021 в 02:38
1
RE: $0 is the name of the current script. Seems wrong to try to cd to it. Мой ответ: В этом контексте $0 — это параметр (каталог), предоставляемый командой find.
 – 
MountainX
19 Сен 2021 в 02:50
@arielf-ReinstateMonica: если вы можете дать рабочий ответ, я его приму. Спасибо.
 – 
MountainX
19 Сен 2021 в 02:52
Как и в случае с FYI, помещение команды в переменную и последующая попытка выполнить эту переменную — плохой шаблон. Постарайтесь этого не делать. В общем, вы должны использовать переменные для хранения данных и создавать функции для хранения команд.
 – 
roaima
19 Сен 2021 в 09:48

2 ответа

Лучший ответ

Два варианта вашего кода, каждый из которых передает специальные значения встроенному скрипту, который вы вызываете из find:

#!/bin/sh

mpath=$1
fn_name=$2
fn_arg=$3

find "$mpath" -type d -exec sh -c '
    cd "$3" && /path/to/my/script.sh "$1" "$2"' sh "$fn_name" "$fn_arg" {} \;

Когда каталог найден, find просто передает значения двух переменных fn_name и fn_arg в скрипт sh -c в качестве двух первых аргументов перед аргументом пути к каталогу. Внутри скрипта мы используем два первых аргумента в качестве аргументов вашего скрипта, а третий — в качестве пути к каталогу cd.

Обратите внимание, что $0 будет содержать строку sh. Оболочка будет использовать эту (произвольную) строку в любых сообщениях об ошибках, которые она может создать (вы показываете пример этого в своем вопросе). Значение $0 не входит в список позиционных параметров.

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

#!/bin/sh

mpath=$1
fn_name=$2
fn_arg=$3

find "$mpath" -type d -exec sh -c '
    fn=$1 arg=$2; shift 2
    for dirpath do
        ( cd "$dirpath" && /path/to/my/script.sh "$fn" "$arg" )
    done' sh "$fn_name" "$fn_arg" {} +

Заменив \; на + в конце команды find, мы вызываем sh -c с пакетами найденных путей к каталогам. Затем встроенный скрипт должен перебирать их и вызывать ваш скрипт для каждого по очереди.

Встроенный скрипт начинает с выбора имени функции и аргумента из списка позиционных параметров и смещает их из этого списка. Затем он перебирает оставшиеся аргументы и вызывает cd и ваш скрипт для каждого. Ниже приведен встроенный скрипт с небольшим добавлением воздуха:

fn=$1    # 1st argument from find
arg=$2   # 2nd argument from find
shift 2  # remove them from the list

# Iterate over the remaining arguments
for dirpath do
    ( cd "$dirpath" && /path/to/my/script.sh "$fn" "$arg" )
done

Я запускаю тело цикла во вспомогательной оболочке, чтобы избежать необходимости «cd назад» каждый раз после запуска вашего скрипта.

Я использую sh здесь, а не bash, так как sh не хватает для запуска этого кода. Кроме того, показанные здесь команды find используют только стандартные функции.

4
Kusalananda 20 Сен 2021 в 23:16
Спасибо. Обе ваши версии действительно работают. Преимущество по сравнению с ответом, который я предоставил, заключается в том, что они работают с относительными путями. Я предпочитаю первый вариант, так как узким местом является выполнение "myscript.sh", а не find.
 – 
MountainX
19 Сен 2021 в 16:02
Помните, что это может привести к неожиданным результатам, если $mpath содержит компоненты .. или установлен $CDPATH. В сценариях рекомендуется использовать CDPATH= cd -P вместо простого cd, чтобы он вел себя как обычный chdir() (у вас все равно будут проблемы с cd и mpath=-, но опять же, find все равно подавится всем, что начинается с -).
 – 
Stéphane Chazelas
20 Сен 2021 в 23:36

Вот мое предлагаемое (и рабочее) решение:

#!/bin/bash
function_name=$1
mpath="$2"
marg=$3

while IFS= read -r -d '' sdir
do
  cd "$sdir"
  /path/to/my/script.sh "$function_name" "$marg"
done <   <(find "$mpath" -type d -print0)

Изменить: в ответ на комментарии это решение не работает с относительными путями.

-1
MountainX 19 Сен 2021 в 16:10
Вы не можете использовать return вне функции. continue может быть лучше.
 – 
ilkkachu
19 Сен 2021 в 07:06
1
Обратите внимание, что если $mpath является относительным путем, то можно ожидать, что только первый cd будет успешным. Дальнейшие вызовы cd попытаются перейти в подкаталоги этого первого каталога.
 – 
Kusalananda
19 Сен 2021 в 11:18