Есть несколько вопросов.
>> Ваша первая команда будет интерпретироваться вашей текущей оболочкой как перенаправление в файл с буквальным именем {} , если он не заключен в кавычки.
*.ovpn может быть расширен с помощью оболочки, прежде чем find когда-либо запущенный. Это произойдет, если у вас есть хотя бы один объект в текущем каталоге, который соответствует шаблону. Вы хотите процитировать это. Сравните этот вопрос.
Вы получаете Can't open echo потому что на самом деле вы говорите sh чтобы открыть echo . Для выполнения команды вам нужен sh -c .
find без указания пути не является переносимым (сравните этот вопрос). Хотя вам это может сойти с рук, я упомяну эту проблему, чтобы сделать ответ более полезным для других пользователей.
Это улучшенная версия вашей первой команды, которая вроде работает (не запускайте ее, продолжайте читать):
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "{}"' \;
Обратите внимание, что я должен был заключить двойную кавычку {} в одинарные кавычки. Эти двойные кавычки "видны" с помощью sh и заставляют имена файлов с пробелами и т.д. Работать как цели перенаправления. Без кавычек вы можете получить как echo "line to append" >> foo bar.ovpn что эквивалентно echo "line to append" bar.ovpn >> foo . Заключение в кавычки делает echo "line to append" >> "foo bar.ovpn" вместо этого.
К сожалению, имена файлов, содержащие " , нарушат этот синтаксис.
Правильный способ передать {} в sh - это не включить его в командную строку, а передать его содержимое в качестве отдельного аргумента:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$0"' {} \;
$0 внутри командной строки расширяется до первого аргумента, который наш sh получает после -c '…' . Теперь даже " в имени файла не нарушит синтаксис.
Обычно (как в скрипте) ссылается на первый аргумент, который вы используете $1 . По этой причине некоторые пользователи предпочитают использовать фиктивный аргумент, равный $0 , например:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' dummy {} \;
Если бы это был скрипт, $0 расширился бы до его имени. Вот почему нередко видеть, что этот dummy самом деле является sh (или bash , если кто-то вызывает bash -c … и т.д.):
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' sh {} \;
Но ждать! find звонки отдельного sh для каждого отдельного файла. Я не ожидаю, что у вас будут тысячи файлов .ovpn , но в целом вы можете захотеть обработать много файлов, не вызывая ненужных процессов. Мы можем оптимизировать подход с помощью tee -a которая может записывать несколько файлов как один процесс:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" | tee -a "$@" >/dev/null' sh {} +
Обратите внимание, {} + , это проходит несколько путей одновременно. Внутри команды, выполняемой sh -c мы получаем их с помощью "$@" , который расширяется до "$1" "$2" "$3" … . В этом случае необязательный аргумент, который заполняет (не используется) $0 является обязательным.
В общем, есть еще одна проблема: почему printf лучше, чем echo? Однако в этом случае вы используете echo без параметров, и полученная строка является статической, так что все должно быть в порядке.