내부 변수를 확장하고 읽는 방법은 무엇입니까?

내부 변수를 확장하고 읽는 방법은 무엇입니까?

Bash 5.0.17(1)이 설치된 원격 CentOS에서 SSH를 통해 실행하는 유일한 사용자는 다음과 같습니다 read web_application_root.

$HOME/www

또는 다음과 같이:

${HOME}/www

또는 다음과 같이:

"${HOME}"/www

또는 다음과 같이:

"${HOME}/www"

확장된 (환경) 변수를 사용하여 출력을 얻는 것을 목표 MY_USER_HOME_DIRECTORY/www로 합니다.

ls -la $HOME/www제대로 작동하는 동안 ls -la $web_application_root모든 예제가 실패합니다. 오류 예제는 다음과 같습니다.

ls: cannot access '$HOME/www': No such file or directory

내가 아는 한, read위의 모든 $HOME변형은 (오류의 작은따옴표로 인해) 문자열로 처리되므로 확장하지 않습니다.

내부 변수를 확장하고 읽는 방법은 무엇입니까?

답변1

변수는 에 전달될 때 확장되지 않습니다 read. $VARs 또는 ${VAR}s가 VAR기존 환경 변수의 이름을 나타내는 경우 (이름이 ASCII 문자 또는 밑줄로 시작하고 그 뒤에 ASCII 숫자 또는 밑줄이 오는 변수만) 다른 모든 단어 확장( $non_exported_shell_variable, $1, $#, ${HOME+x}, $((1 + 1)), $(cmd)...) 변경하지 않으려면 envsubst(GNU에서 gettext) 다음을 사용할 수 있습니다.

IFS= read -r web_application_root || exit
web_application_root=$(printf %s "$web_application_root" | envsubst)
ls -la -- "$web_application_root"

변수 이름을 인수로 사용하고 읽기 및 환경 변수 확장을 수행하는 쉘 함수로 만들 수 있습니다.

read_one_line_and_expand_envvars() {
  IFS= read -r "$1"
  ret="$?"
  command eval "$1"'=$(printf %s "${'"$1"'}" | envsubst)' && return "$ret"
}

예를 들어 다음과 같이 사용됩니다.

printf >&2 'Please enter the root dir (${ENVVAR} expanded): '
read_one_line_and_expand_envvars web_application_root || exit
printf >&2 'The expanded version of your input is "%s"\n' "$web_application_root"

제한된 환경 변수 세트로 대체를 제한하려면 $VAR1$VAR2...목록을 리터럴 인수로 다음에 전달할 수 있습니다 envsubst.

web_application_root=$(
  printf %s "$web_application_root" |
    envsubst '$HOME$MYENVVAR'
)

( 여기서는 envsubst입력에서 $HOME, ${HOME}및 만 바꾸고 다른 모든 s는 변경하지 않도록 지시합니다 .)$MYENVVAR${MYENVVAR}$VAR

모든 형태의 단어 확장을 허용하려면(그러나 이것이 명령 주입 취약점이 된다는 점에 유의하십시오) 다음을 수행할 수 있습니다.

web_application_root=$(eval "cat << __EOF__
$web_application_root
__EOF__")

또는 변수 이름을 인수로 사용하는 함수로:

read_one_line_and_perform_shell_word_expansions() {
  IFS= read -r "$1"
  ret=$?
  command eval '
    case "${'"$1"'}" in
      (EOF) ;;
      (*)
        '"$1"'=$(command eval "cat << EOF
${'"$1"'}
EOF")
    esac' && return "$ret"
}
printf >&2 'Please enter the root dir ($var/$((...))/$(cmd) allowed): '
read_one_line_and_perform_shell_word_expansions web_application_root || exit
printf >&2 'The expanded version of your input is "%s"\n' "$web_application_root"

자세한 인라인 문서와 동일한 기능:

read_one_line_and_perform_shell_word_expansions() {
  # first argument of our function is the variable name or REPLY
  # if not specified.
  varname=${1-REPLY}

  # read one line from stdin with read's unwanted default post-processing
  # (which is otherwise dependant on the current value of $IFS) disabled.
  IFS= read -r "$varname"

  # record read's exit status. If it's non zero, a full line could not be
  # read. We may still want to perform the expansions in whatever much
  # was read, and pass that exit status to the caller so they decide what
  # to do with it.
  ret=$?

  # We prefix the "eval" special builtin with "command" to make it lose
  # its "special" status (namely here, exit the script about failure,
  # something bash only does when in standard mode).
  command eval '
    # the approach we take to expand word expansions would be defeated
    # if the user entered "EOF" which is the delimiter we chose for our
    # here-document, so we need to handle it as a special case:
    case "${'"$varname"'}" in
      (EOF) ;;
      (*)
        # the idea here is to have the shell evaluate the
        # myvar=$(command eval "cat << EOF
        # ${myvar}
        # EOF")
        #
        # shell code when $1 is myvar, so that the
        #
        # cat << EOF
        # contents of $myvar with $(cmd), $ENV and all
        # EOF
        #
        # shell code be evaluated, and those $(cmd), $ENV expansions
        # performed in the process
        '"$varname"'=$(command eval "cat << EOF
${'"$varname"'}
EOF")
    esac' &&
      # unless eval itself failed, return read's exit status to the caller:
      return "$ret"
}

하지만 문제는 XY 문제처럼 들립니다. 그렇게 해서 입력을 얻는 것은 read번거롭고 비실용적입니다. 인수를 통해 입력을 얻는 것이 더 낫습니다. 그런 다음 확장을 위해 호출자의 셸에 맡길 수 있습니다.그들을그렇게 할 생각입니다.

바꾸다

#! /bin/sh -
IFS= read -r var
ls -l -- "$var"

(그리고 기억해전화하는 read것과 IFS=전화하지 않는 -r것은 거의 당신이 원하는 것이 아니다).

완료:

#! /bin/sh -
var=${1?}
ls -l -- "$var"

그런 다음 호출자는 적합하다고 판단되는 작업 your-script ~/dir을 수행 할 수 있습니다.your-script "$HOME/dir"your-script '$$$weird***/dir'your-script $'/dir\nwith\nnewline\ncharacters'


^단어 확장이러한 맥락에서는 다음을 가리킨다.매개변수 확장,산술 확장그리고명령 대체. 여기에는 포함되지 않습니다.파일 이름 생성(일명와일드카드또는경로명 확장),물결표 확장...도 아니다버팀대 확장(표준 sh기능 자체는 아닙니다). 여기에서 문서를 사용하면 '"가 변경되지 않은 상태로 유지되지만 여전히 백슬래시 처리가 있다는 점에 유의하세요.

관련 정보