
Я могу подключиться по SSH и перейти в каталог, сохраненный в локальной переменной $out_dir
. Однако я не могу понять, почему find | head
команда не работает. ВПопытка №2, я добавил вторую обратную косую черту в конце команды find
:
Ошибка №1: find: `/dir/on/remote/server': No such file or directory
ssh $user@$ip << EOF
discard=$( find $out_dir -exec basename {} \; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
logout
EOF
Ошибка №2: syntax error near unexpected token `|'
ssh $user@$ip << EOF
discard=$( find $out_dir -exec basename {} \\; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
logout
EOF
решение1
Причина, по которой вы получаете сообщение «Нет такого файла или каталога» при попытке № 1, заключается в том, что вся интерполяция переменных и команд для heredoc ( <<EOF
) выполняется локально, до его отправки на удаленный хост.
Как вы правильно заметили, $out_dir
интерполируется; вы видите каталог, который хотите видеть. Это происходит на вашей локальной машине до того, как вы сделаете вызов ssh. Подстановка команды ( $()
) также происходит локально (но вы намереваетесь сделать это удаленно). Другими словами, весь find обрабатывается локально перед отправкой на удаленную машину — все, что упаковано, $()
обрабатывается heredoc. Таким образом, кажется, что ${out_dir}
его нет на вашей локальной машине — «Нет такого файла или каталога».
Если вы хотите увидеть это сами, давайте упростим пример. Попробуйте это:
$ ssh foo@localhost <<EOF
echo "using account: $(whoami)"
EOF
Pseudo-terminal will not be allocated because stdin is not a terminal.
<truncated>
using account: vagrant
Очевидно, что содержимое $() выполняется локально, так как моя локальная учетная запись — «vagrant»; если бы все работало правильно, учетная запись была бы «foo», поскольку я указал ssh foo@localhost
.
Попытка №2 не работает, так как вы экранировали '\' (а не ';'). Find требует завершения -exec
, а теперь его нет. Вместо этого команда резко завершается, и у вас есть висячий ';', который завершает команду bash. '|' ожидает пересылки некоторого ввода, но ничего не указано. По сути, вы создали это:
$ | cat
-bash: syntax error near unexpected token `|'
Итак, какое же решение работает?
Ну, find
могу удалить за вас:
ssh $user@$ip <<EOF
find $out_dir -delete
EOF
Это просто и прямолинейно. Если вы беспокоитесь о выборе некоторых файлов и исключении других, пересмотрите find
.
Я даю совет «пересмотреть find
», потому что в вашей текущей версии, похоже, вы пытаетесь выполнить обработку пограничных случаев:
- использование базового имени
- использование
head -n -1
Но я не ожидаю, что это решение сработает так, как вы задумали.
Во-первых, вызов basename
удалит большую часть пути, а в вашей команде ssh вы ничего не делаете для смены каталога. Ваше выполнение ssh попытается удалить все относительно домашнего каталога ${user} -- и, указывая ${out_dir}, вы, похоже, хотите использовать каталог, отличный от домашнего! Опять же, просто позвольте find
удалить за вас.
Во-вторых, возможно, я ошибаюсь, но я подозреваю, что вы используете его, head -n -1
чтобы избавиться от ${out_dir} из списка целевых файлов.
$ find junk/
junk/
junk/one
junk/two
junk/three
Правильно, вы не хотите удалять junk/
.
Однако попробуйте сделать следующее:
$ seq 1 4
1
2
3
4
$ seq 1 4 | head -n -1
1
2
3
Это не сохраняет первую строку, а сохраняет последнюю. Попробуйте tail:
$ seq 1 4 | tail -n +2
2
3
4
Но опять же, просто позвольте нам find
удалить все за вас.
Если вам нужно найти только для удаления файлов, посмотрите -type f
. Но есть также шаблоны исключения и включения для более сложной фильтрации.
решение2
Попробуй это:
ssh "$user@$ip" "out_dir=$out_dir;" '
discard=$( find $out_dir -exec basename {} \; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
'
Примечание:предполагаемую удаленную команду можно было бы сделать проще и безопаснее, но придирки к этому только запутают суть.
В принципе, вам следует пройти те части, которыеа)должентакжебыть расширен наместныймашина и те, которыеб)должно бытьтолькобыть расширен наудаленныймашина какотдельныйаргументы ssh, первый в двойных кавычках, второй в одинарных.
Ssh объединит все аргументы «команды» пробелами, прежде чем передать их как один аргумент опции -c
входа пользователя в систему на удаленной машине.
Если удаленная команда сама должна содержать одинарные кавычки, вы можете использовать подстановку команд + here doc combo. Кроме того, если значение "локальной" переменной может содержать пробелы и другие специальные символы, вы должны экранировать их:
out_dir='/path/to/ * happiness * '
out_dir=$(printf %s "$out_dir" | sed 's/[^a-zA-Z0-9_/.]/\\&/g')
ssh localhost out_dir="$out_dir;" "$(cat <<'EOT'
echo '<$out_dir>' = "<$out_dir>"
EOT
)"
Обратите внимание, что если одинарные кавычки в <<'EOF'
; опущены, то документ $out_dir
в here doc будет расширен (на локальной машине).
В последних версиях bash используется такая ${var@Q}
форма, поэтому там вы можете просто использовать "out_dir=${outdir@Q};"
вместо этого уродливого echo | sed
.
Передача удаленного скрипта через stdin требуется редко (если вообще требуется!).