変数値にスペースが含まれている場合、区切り文書の変数をパラメータに拡張するにはどうすればよいですか?

変数値にスペースが含まれている場合、区切り文書の変数をパラメータに拡張するにはどうすればよいですか?

lftp経由でアップロードするためのスクリプトを作成しました。

#! /usr/bin/bash
set -xe

if [ -n "$1" ]; then
  # ...
else
  source="."
  # target=name of current local folder
  target="${PWD##*/}"/
  cmd="mirror --reverse --continue --parallel=5 "$source" "$target""
fi


lftp -u $user,$pass $host << EOF
set log:enabled true

eval "$cmd"

quit
EOF

現在のディレクトリにスペースがない場合は正常に動作します。この問題は、現在のディレクトリにスペースがある場合に発生します。デバッグ情報は次のように表示されます。

➜  upload.sh
+ '[' -n '' ']'
+ source=.
+ target='Two Words/'
+ cmd='mirror --reverse --continue --parallel=5 . Two Words/'

Twoこの問題により、スクリプトは代わりに名前付きディレクトリにアップロードされますTwo Words

特に、どの行が私が間違っているのかわからないので、この問題をどのように解決するのかわかりませんcmd=...eval "$cmd"?どれでもない?両方とも?

とにかくデバッグ出力は次のようになると予想しましたが(二重引用符を+ cmd='mirror --reverse --continue --parallel=5 "." "Two Words/"'参照)、なぜそうでないのかわかりません。

この問題は、これまで私が調査した他の多くの問題に似ています。 Googleでこれが異なる点/難しいのは、拡張が「ここの文字列」内で発生することです。私が知っている限り、それは重要ではありませんが、私が知っている限り、それは非常にユニークです。


コメントでは、すべてを配列に保存するよう提案しました。私の試みは次のとおりです。

#! /usr/bin/bash
set -xe

if [ -n "$1" ]; then
  cmd=(mput -c -P 5 "$1")
  if [ -n "$2" ]; then
    cmd=(mkdir -p "$2"
        mput -c -P 5 -O "$2" "$1") #*
  fi
else
  source="."
  # target=name of current local folder
  target="${PWD##*/}"/
  cmd=(mirror --reverse --continue --parallel=5 "$source" "$target")
fi


lftp -u $user,$pass $host << EOF

"${cmd[@]}"

quit
EOF

(*サブシェルに複数行を含める方法がわからないので、実際の改行を入れましたが、それが正しいのかどうかわかりません。Googleで検索した結果はすべてコマンドの置き換えに関連しています。サブシェルではありません。)

これを実行すると、次のエラーが発生します。Unknown command 'mirror --reverse --continue --parallel=5 . Two Words/'.これをコピーしてシェルに貼り付けると、他のエラーが発生するため、何が起こっているのか理解できません。

電話でもlftp -u $user,$pass $host -e "${cmd[@]}"接続できません。このエラーが発生します。

open: unrecognized option '--reverse'
Usage: lftp [-e cmd] [-p port] [-u user[,pass]] <host|url>

lftp ... -e "$("${cmd[@]}")"このエラーは、次の実行時に発生します。

open: option requires an argument -- 'e'
Usage: lftp [-e cmd] [-p port] [-u user[,pass]] <host|url>

ご覧のとおり、現在私はランダムに試しています。これは良い戦略ではありません。

ベストアンサー1

sコマンドの言語構文からこれらのパラメーターを引用する必要があります(または少なくともlftps言語の対応するスペースまたはその他の特殊文字をエスケープする必要があります)。lftpmirrorlftp

lftp簡単な引用フォームをサポートします。ほとんどのシェルと同様に、単一引用符、二重引用符、およびバックスラッシュを使用できますが、構文は異なります。lftpのコマンドを使って実験してみることができますecho

lftp :~> echo \'
'
lftp :~> echo \a
\a
lftp :~> echo \
> a
a

\エスケープ演算子ですが、特定の文字に対してのみ機能します。改行文字が後に続く場合は行連続文字で、改行文字をエスケープしません。

lftp :~> echo "a\'"
a\'
lftp :~> echo "a\"b"
a"b
lftp :~> echo "a\$b"
a\$b

二重引用符内でエスケープする文字のリストはさらに減ります。"そこにゴミの欠片があるかもしれません"..."\"

lftp :~> echo 'a\'b'
a'b
lftp :~> echo 'a\\b'
a\b

一重引用符内でも同様です。'...'そこにある引用符は、shなどのシェルの強い引用符とは異なります。

lftp :~> echo $'a\nb'
$a\nb

$'...'ksh93スタイルの引用フォームはサポートされていません。

lftp :~> echo 'foo
foo
lftp :~> echo 'a\
b'
ab

引用符はペアで表示する必要はありません。引用符内にあるかどうかにかかわらず、コマンドパラメータに改行文字を含めることはできません。

'...'コードを見てエスケープする必要がある文字は、"..."バックスラッシュとその引用符文字だけです。 lftpバイトレベルで作業すると、バイトシーケンスは文字としてデコードされません。

したがって、構文内の文字列を適切に引用する最善の方法lftpは、すべて'および\その中(実際には0x5cバイトまたは他のマルチバイト文字エンコーディングのバイト)\とPackaged inをエスケープすることです'...'。または"..."

ここで使用するevalことは不要で、2つのレベルの参照を管理する必要があるため、より複雑になります。

#! /usr/bin/bash -
set -xe

lftp_quote() {
  local LC_ALL=C
  local -n var
  for var do
    if [[ $var = *$'\n'* ]]; then
      printf >&2 '%s\n' "\"$var\" contains newline characters. That's not supported by lftp"
      exit 1
    fi
    var=${var//'\'/'\\'}
    var=${var//"'"/"\'"}
    var="'$var'"
  done
}
  
if (( $# > 0 )); then
  ...
else
  source="."
  # target=name of current local folder
  target="${PWD##*/}"/
  lftp_quote source target
  cmd="mirror --reverse --continue --parallel=5 -- $source $target"
fi

lftp_quote user pass host
lftp << EOF
set log:enabled true
open --user $user --password $pass -- $host && $cmd
EOF

(パスワードは公開なので、ここからコマンドラインにパスワードを渡さないでください。)

これらの文字列をそのままコマンドに渡すことができますが、FTPプロトコルのコマンドでこれらのファイル名を適切にエスケープするかどうかは別の問題lftpですmirrorlftpFTPはこの点で信頼できないものとして悪名高いもので、サーバーごとに異なる動作をします。lftpFTPに加えて、他の多くのファイル転送プロトコルもサポートされており、それらのいくつかはより安定しています。

おすすめ記事