Команда SSH find не распознает каталог, хранящийся в локальной переменной

Команда SSH find не распознает каталог, хранящийся в локальной переменной

Я могу подключиться по 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 требуется редко (если вообще требуется!).

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