[linux] stdin은 터미널이 아니므로 의사 터미널이 할당되지 않습니다.

원격 서버에 일부 디렉토리를 만든 다음 scp를 사용하여 로컬 컴퓨터에서 원격으로 파일을 복사하는 쉘 스크립트를 작성하려고합니다. 여기까지 내가 가진 것입니다 :

ssh -t user@server<<EOT
DEP_ROOT='/home/matthewr/releases'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
    echo "creating the root directory"
    mkdir $DEP_ROOT
fi
mkdir $REL_DIR
exit
EOT

scp ./dir1 user@server:$REL_DIR
scp ./dir2 user@server:$REL_DIR

실행할 때마다 다음 메시지가 나타납니다.

Pseudo-terminal will not be allocated because stdin is not a terminal.

그리고 스크립트는 영원히 중단됩니다.

내 공개 키는 서버에서 신뢰할 수 있으며 스크립트 외부의 모든 명령을 잘 실행할 수 있습니다. 어떤 아이디어?



답변

시도 ssh -t -t(또는 ssh -tt표준 입력 단자가 아닌 경우에도 힘 의사 TTY 할당을 짧게).

참조 : bash 스크립트로 실행 된 SSH 세션 종료

ssh 맨 페이지에서 :

-T      Disable pseudo-tty allocation.

-t      Force pseudo-tty allocation.  This can be used to execute arbitrary 
        screen-based programs on a remote machine, which can be very useful,
        e.g. when implementing menu services.  Multiple -t options force tty
        allocation, even if ssh has no local tty.


답변

또한 옵션 -T에서 수동

의사 -tty 할당 비활성화


답변

zanco의 대답 , 당신은에 원격 명령을 제공하지 않는 ssh쉘 명령 줄을 구문 분석하는 방법을 제공. 이 문제를 해결하려면 ssh명령 호출 구문을 변경하여 원격 명령이 구문 상 올바른 다중 행 문자열로 구성되도록하십시오.

사용할 수있는 다양한 구문이 있습니다. 예를 들어, 명령을 bashand sh및 다른 쉘로 파이프 할 수 있으므로 가장 간단한 해결책은 ssh쉘 호출을 heredocs와 결합 하는 것입니다.

ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

위의 내용 없이 실행 /bin/bash하면 경고가 표시 Pseudo-terminal will not be allocated because stdin is not a terminal됩니다. 또한 EOT작은 따옴표 bash묶여서 heredoc을 nowdoc 로 인식하여 로컬 변수 보간을 끄고 명령 텍스트가 그대로 전달되도록하십시오 ssh.

파이프 팬이라면 다음과 같이 위의 내용을 다시 쓸 수 있습니다.

cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

위에도 동일한 경고가 /bin/bash적용됩니다.

또 다른 유효한 방법은 bash다음과 같이 여러 계층의 변수 보간을 사용하여 여러 줄 원격 명령을 단일 문자열로 전달하는 것입니다.

ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"

위의 솔루션은 다음과 같은 방식으로이 문제를 해결합니다.

  1. ssh user@serverbash에 의해 구문 분석되고으로 해석되어 ssh명령, 인수 다음에 user@server받는 전달되는 ssh명령

  2. "완료 될 때 ssh명령에 전달 될 인수를 구성하는 보간 된 문자열을 시작합니다 .이 경우이 ssh명령은 다음과 같이 실행할 원격 명령 으로 해석 됩니다.user@server

  3. $( 주변 보간 된 문자열에 의해 출력이 캡처되어 실행될 명령을 시작합니다.

  4. cat뒤에 오는 모든 파일의 내용을 출력하는 명령입니다. 의 출력은 cat캡처 보간 문자열로 다시 전달됩니다.

  5. <<bash heredoc을 시작합니다

  6. 'EOT'heredoc의 이름이 EOT임을 지정합니다. 'EOT를 둘러싼 작은 따옴표 는 heredoc이 nowdoc 로 구문 분석되어야 함을 지정합니다. nowdoc 는 내용이 bash에 의해 보간되지 않고 리터럴 형식으로 전달 되는 heredoc 의 특수 형식입니다.

  7. 사이에 <<'EOT'있고 <newline>EOT<newline>nowdoc 출력에 추가 될 모든 컨텐츠

  8. EOTnowdoc을 종료하여 nowdoc 임시 파일을 작성하여 호출 cat명령으로 다시 전달 합니다. catnowdoc를 출력하고 출력을 캡처 된 보간 된 문자열로 다시 전달

  9. ) 실행할 명령을 종료합니다

  10. "보간 문자열 캡처를 마칩니다. 보간 된 문자열의 내용은 ssh단일 명령 행 인수로 다시 전달되어 다음 과 같이 ssh실행할 원격 명령으로 해석됩니다.user@server

과 같은 외부 도구를 사용 cat하지 말고 하나 대신 두 개의 명령문을 사용하지 않아도 read되는 경우 heredoc과 함께 내장을 사용하여 SSH 명령을 생성하십시오.

IFS='' read -r -d '' SSH_COMMAND <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

ssh user@server "${SSH_COMMAND}"


답변

이 오류는 동일한 오류 메시지로 발생하는 관련 문제를 해결했기 때문에 추가합니다.

문제 : Windows에서 cygwin을 설치 했는데이 오류가 발생했습니다.Pseudo-terminal will not be allocated because stdin is not a terminal

해결 : openssh 클라이언트 프로그램과 유틸리티를 설치 하지 않은 것으로 나타났습니다 . 그 cygwin 때문에 cygwin 버전이 아닌 ssh의 Windows 구현을 사용하고있었습니다. 해결책은 openssh cygwin 패키지를 설치하는 것이 었습니다.


답변

경고 메시지 Pseudo-terminal will not be allocated because stdin is not a terminal.sshstdin이 here 문서에서 리디렉션되는 동안 명령이 지정되지 않았기 때문입니다 . 인수로 지정된 명령이 없기 때문에 ssh먼저 원격 호스트에 pty를 할당해야하는 대화식 로그인 세션이 필요하지만 로컬 stdin이 tty / pty가 아님을 인식해야합니다. sshhere 문서에서 stdin을 리디렉션 하려면 일반적으로 명령 (예 /bin/sh:)을 인수로 지정해야합니다 ssh. 이러한 경우 기본적으로 원격 호스트에 pty가 할당되지 않습니다.

통해 실행 할 명령이 없기 때문에 ssh그 (같은 TTY / PTY의 존재를 필요로 vim하거나 top) -t로 전환 ssh불필요하다. 그냥 사용 ssh -T user@server <<EOT ...또는 ssh user@server /bin/bash <<EOT ...과 경고가 사라집니다.

경우 <<EOF이스케이프되지 않았거나 작은 따옴표 (즉, <<\EOT또는 <<'EOT'그것을 실행하기 전에 여기에 문서 내부) 변수는 로컬 쉘에 의해 확장됩니다 ssh .... 결과는 here 문서 내부의 변수가 원격 쉘에서만 정의되므로 비어있게됩니다.

따라서 $REL_DIR로컬 셸에서 액세스 할 수 있고 원격 셸에서 $REL_DIR정의되어야하는 경우 ssh명령 전에 here 문서 외부에서 정의해야합니다 ( 아래 버전 1 ). 경우 나, <<\EOT또는 <<'EOT'사용하는 경우, 출력 ssh명령이 할당 될 수 REL_DIR의 유일한 출력 경우 ssh표준 출력 명령에 의해 genererated되는 echo "$REL_DIR"(가) / 단일 여기 인용 된 문서 (탈출 내부 버전 2 이하에게).

세 번째 옵션은 here 문서를 변수에 저장 한 다음이 변수를 명령 인수로 전달합니다 ssh -t user@server "$heredoc"( 아래 버전 3 ).

그리고 마지막으로, 원격 호스트의 디렉토리가 성공적으로 작성되었는지 확인하는 것은 좋지 않습니다 ( ssh를 사용하여 원격 호스트에 파일이 있는지 확인 참조 ).

# version 1

unset DEP_ROOT REL_DIR
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"

ssh localhost /bin/bash <<EOF
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
#echo "$REL_DIR"
exit
EOF

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"


# version 2

REL_DIR="$(
ssh localhost /bin/bash <<\EOF
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
exit
EOF
)"

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"


# version 3

heredoc="$(cat <<'EOF'
# -onlcr: prevent the terminal from converting bare line feeds to carriage return/line feed pairs
stty -echo -onlcr
DEP_ROOT='/tmp'
datestamp="$(date +%Y%m%d%H%M%S)"
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
stty echo onlcr
exit
EOF
)"

REL_DIR="$(ssh -t localhost "$heredoc")"

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"


답변

모든 관련 정보는 기존 답변에 있지만 실용적인 요약을 시도해 보겠습니다 .

tl; dr :

  • 명령 행 인수를 사용하여 실행할 명령을 전달하십시오 .
    ssh jdoe@server '...'

    • '...' 문자열은 여러 줄에 걸쳐있을 수 있으므로 여기 문서를 사용하지 않아도 코드를 읽을 수 있습니다.
      ssh jdoe@server '
      ...
      '
  • here-document 를 사용할 때와 같이 stdin을 통해 명령을 전달하지 마십시오 .
    ssh jdoe@server <<'EOF' # Do NOT do this
    ...
    EOF

명령을 인수로 전달하면 그대로 작동합니다.

  • 의사 터미널의 문제는 발생하지 않습니다.
  • exit명령이 처리 된 후 세션이 자동으로 종료되므로 명령 끝에 명령문이 필요하지 않습니다 .

간단히 말해 : stdin을 통해 명령을 전달 하는 것은 ssh의 설계와 상충되는 메커니즘 이며 문제를 해결해야합니다.
더 알고 싶다면 계속 읽으십시오.


선택적 배경 정보 :

ssh대상 서버에서 실행할 명령을 받아들이는 메커니즘은 명령 행 인수입니다 . 최종 피연산자 (옵션이 아닌 인수)는 하나 이상의 쉘 명령이 포함 된 문자열을 허용합니다.

  • 기본적으로 이러한 명령 은 (의사) 터미널을 사용하지 않고 비 대화식 셸 에서 무인으로 실행되며 (옵션 -T이 암시 됨) 마지막 명령이 처리를 마치면 세션이 자동으로 종료 됩니다.

  • 대화식 프롬프트에 응답하는 것과 같이 명령에 사용자 상호 작용이 필요한 경우 , 옵션을 사용하여 원격 세션과 상호 작용할 수있는 의사 터미널 인 pty (pseudo-tty) 작성을 명시 적으로 요청할 수 있습니다 -t. 예 :

    • ssh -t jdoe@server 'read -p "Enter something: "; echo "Entered: [$REPLY]"'

    • 대화식 read프롬프트는 pty에서만 올바르게 작동하므로 -t옵션이 필요합니다.

    • pty를 사용하면 주목할만한 부작용이 있습니다. stdout과 stderr이 결합 되고 stdout을 통해보고됩니다 . 다시 말해, 정규 출력과 오류 출력의 구별을 잃습니다. 예 :

      • ssh jdoe@server 'echo out; echo err >&2' # OK - stdout and stderr separate

      • ssh -t jdoe@server 'echo out; echo err >&2' # !! stdout + stderr -> stdout

이 인수가 없으면ssh 문제가 시작되는 stdin을 통해 명령을 보낼 때를 포함 하여 대화식 쉘을 만듭니다 .

  • 를 들어 대화 형 쉘, ssh일반적으로 기본적으로 PTY (의사 터미널) 할당 을 제외하고 는 표준 입력이 터미널 A (실제)에 연결되지 않은 경우.

    • stdin을 통해 명령을 보내면 ssh의 stdin이 더 이상 터미널에 연결되어 있지 않으므로 pty가 생성 되지 않으며 이에 따라 ssh 경고 합니다 .
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • 심지어 -t그 표현 목적입니다 옵션, 요청 PTY의 창조이며, 하지 충분 이 경우 : 동일한 경고를 얻을 것이다.

      • 다소 호기심, 당신은해야한다 두 배-t 옵션 하십시오 PTY의 힘 창출 ssh -t -t ...또는 ssh -tt ...당신이 있음을 나타냅니다 정말, 정말 그것을 의미를 .

      • 아마도이 매우 신중한 단계를 요구하는 근거는 상황이 예상대로 작동하지 않을 수 있다는 입니다. 예를 들어, macOS 10.12에서 stdin을 통해 명령을 제공하고을 사용하여 위 명령과 똑같은 기능이 제대로 작동 -tt하지 않습니다 . read프롬프트에 응답 한 후 세션이 중단 됩니다.
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


인수로 전달하려는 명령이 시스템에 대해 명령 행을 너무 길게 만드는 경우 (길이가 가까울 경우 getconf ARG_MAX이 기사 참조 ), 먼저 스크립트 형식으로 원격 시스템에 코드를 복사하는 것을 고려 하십시오 ( )를 사용하여 scp해당 스크립트를 실행하는 명령을 보냅니다.

핀치에서을 사용 -T하고 stdin을 통해 exit명령 에 후행 명령을 제공하십시오. 그러나 대화식 기능이 필요한 경우 -tt대신 -T사용할 수 없습니다.


답변

중단이 어디에서 왔는지 모르겠지만 대화 형 ssh로 명령을 리디렉션 (또는 파이핑)하는 것이 일반적으로 문제의 레시피입니다. command-to-run-as-a-last-argument 스타일을 사용하고 ssh 명령 행에서 스크립트를 전달하는 것이 더 강력합니다.

ssh user@server 'DEP_ROOT="/home/matthewr/releases"
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
    echo "creating the root directory"
    mkdir $DEP_ROOT
fi
mkdir $REL_DIR'

(하나의 거대 '구분 된 여러 줄 명령 줄 인수에서 모두).

의사 터미널 메시지는 -tssh가 원격 시스템에서 실행되는 환경을 거기서 실행되는 프로그램의 실제 터미널처럼 보이도록 요청하기 때문입니다. ssh 클라이언트는 자체 표준 입력이 터미널이 아니기 때문에 원격 시스템에서 로컬 터미널의 실제 터미널로 특수 터미널 API를 전달할 수 없습니다.

-t어쨌든 무엇 을 달성하려고 했습니까?