변수 값에 공백이 포함된 경우 구분된 문서의 변수를 매개변수로 확장하는 방법은 무엇입니까?

변수 값에 공백이 포함된 경우 구분된 문서의 변수를 매개변수로 확장하는 방법은 무엇입니까?

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것은 불필요하며 두 가지 수준의 참조를 관리해야 하기 때문에 더 복잡해집니다.

#! /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입니다 mirror. lftpFTP는 이와 관련하여 신뢰할 수 없는 것으로 악명 높으며 서버마다 다르게 작동합니다. lftpFTP 외에도 다른 많은 파일 전송 프로토콜을 지원하며 그 중 일부는 더 안정적입니다 .

답변2

특히 어떤 줄이 내가 잘못되고 있는지 모르기 때문에 이 문제를 어떻게 해결해야 할지 모르겠습니다 cmd=.... eval "$cmd"? 어느 것도 아니다? 둘 다?

어쨌든 디버그 출력이 다음과 같을 것으로 예상했지만( + cmd='mirror --reverse --continue --parallel=5 "." "Two Words/"'큰따옴표 참고) 왜 그렇지 않은지 잘 모르겠습니다.

당신이 가지고 있는 것은 다음과 같습니다:

cmd="mirror --reverse --continue --parallel=5 "$source" "$target""

첫 번째 "(at cmd=")는 인용된 문자열을 시작하고, 두 번째(at "$source)는 이를 끝내며, 다음 문자열에도 동일하게 적용됩니다. 결과 문자열에는 따옴표가 없습니다. 당신은 사용해야합니다

cmd="mirror ... \"$source\" \"$target\""

문자열에 큰따옴표를 추가하거나 lftp에 작은따옴표를 사용할 수 있는 경우 다음을 사용할 수 있습니다.

cmd="mirror ... '$source' '$target'"

cmd=(mirror --reverse --continue --parallel=5 "$source" "$target")
lftp -u $user,$pass $host << EOF

"${cmd[@]}"

이 명령을 실행하면 Unknown command 'mirror --reverse --continue --parallel=5 오류가 발생합니다. 두 단어/'.

훌륭한. 확장은 "${array[@]}"배열 요소를 여러 개의 다른 셸 단어(문자열)로 확장하고 파일 이름에 공백이 그대로 유지되므로 셸에서 유용합니다. 하지만 여기서는 here-doc에 있으므로 여러 단어/문자열을 생성하는 것이 불가능합니다. 전체 here-doc는 단지 하나의 문자열입니다. 따라서 배열 요소를 공백으로 연결하고 해당 값으로 확장합니다. 구분된 문서와 마찬가지로 따옴표가 보존됩니다.

lftp로 얻은 명령은 다음과 같습니다.

"mirror --reverse --continue ..."

따옴표로 인해 단일 명령으로 처리됩니다.

lftp :~> "echo foo"
Unknown command `echo foo'.
lftp :~> echo foo
foo

질문 제목에서는 "구분된 문서의 변수를 매개변수로 [확장]"에 대해 묻지만 그렇게 하는 것이 불가능하기 때문에 질문이 아닙니다. 어쨌든 여기에 문서가 있습니다). 아니요, 문제는 lftp가 올바르게 인용하는 데 있습니다.

이것을 복사하여 쉘에 붙여 넣으면 다른 오류가 발생하므로 무슨 일이 일어나고 있는지 이해할 수 없습니다.

아마 이런 게 있지 않을까요 bash: mirror: command not found? lftp에 대한 명령이 있으므로 mirror이를 쉘의 명령줄에 붙여넣는 것이 도움이 될지 확실하지 않습니다.

쉘 명령을 저장하기 위해 배열을 사용하라는 제안을 이해합니다.일부가 있습니다매우 좋은이유. 하지만 여기에는 쉘 명령이 없으므로 불필요합니다.

전화로 lftp -u $user,$pass $host -e "${cmd[@]}"도 연결이 안 됩니다. 이 오류가 발생합니다.

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

여기에서는 명령줄의 배열 내용을 lftp에 전달합니다. [@]배열 요소는 다음을 사용했기 때문에 다른 인수로 전달됩니다.

lftp -u USER,PASSWORD HOSTNAME -e mirror --reverse --continue --parallel=5 SOURCE TARGET

(물론 , $user, $host$source확장되었습니다.)$target

-e옵션은 명령을 인수로 사용합니다. 이 경우 명령은 입니다 mirror. 다음 옵션은 --reverse다른 명령줄 옵션으로 처리되며 lftp에서 인식되지 않습니다. 여기서 당신이 "${cmd[*]}"사용 하고 싶은피하다-elftp의 옵션은 어쨌든 문자열만 사용할 수 있으므로 배열 요소를 고유한 인수로 사용합니다 .

반면에 단일 문자열로만 작동할 수 있으므로 배열을 사용하는 것은 애초에 유용하지 않습니다.

lftp ... -e "$("${cmd[@]}")"이 오류는 다음을 실행할 때 발생합니다.

$(...)쉘에서 내부적으로 명령으로 실행되는 명령 대체 가 있습니다 . 어디서 얻으셨는지 모르겠지만, 예를 들어 시도해 보세요.

$ cmd=(date)
$ lftp ...  -e "$("${cmd[@]}")"

(아마도 거기에 없다는 사실에 감사해야 할 것입니다 rm something.)


Stéphane의 답변은 대부분의 특수 문자 인용을 처리하는 솔루션을 제공하지만 더 간단한 것을 원한다면당신은 공백 문자에 대해서만 걱정하면 된다고 생각합니다., 내 생각에는 이것이 효과가 있을 것 같습니다:

#! /usr/bin/bash
set -xe

source="."
target="Two Words/"

cmd="mirror --reverse --continue --parallel=5 '$source' '$target'"

user=foo
pass=secret

lftp -u "$user,$pass" http://localhost << EOF
set log:enabled true
$cmd
quit
EOF

어차피 배열은 결국 문자열이 되기 때문에 배열이 필요하지 않습니다. 결과 문자열에서 파일 이름 주위에 따옴표가 필요합니다. 왜냐하면 그 안에 작은 따옴표를 사용했기 $cmd때문입니다.이것은 게으른 해결책입니다, 파일 이름에 따옴표도 포함되어 있는지는 상관하지 않습니다. 대신 큰따옴표를 사용할 수 있습니다 "... \"$source\"...". 여기에 있는 문서는 다음과 같이 표시됩니다.

set log:enabled true
mirror --reverse --continue --parallel=5 '.' 'Two Words/'
quit

이것은 lftp에 전달하는 합리적인 명령 세트인 것 같습니다.

물론, lftp 딸랑이를 확인하기 전에 따옴표 없이 파일 이름을 테스트하는 것이 비교적 간단합니다.

case $target in
    *[\'\"]*) echo "ugh, can't have quotes in filename!"; exit 1;;
esac

또는 더 엄격하게 허용되는 문자를 나열하십시오.

case $target in
    *[![:alnum:]._" "/]*) echo "ugh, disallowed characters in filename!"; exit 1;;
esac

관련 정보