사용자 입력을 기반으로 Bash 변수 확장을 직접 수행할 수 있습니까?

사용자 입력을 기반으로 Bash 변수 확장을 직접 수행할 수 있습니까?

Bash에서 사용자 입력을 읽으면서도 bash변수 확장을 허용하는 방법이 있습니까?

프로그램 중간에 사용자에게 경로를 입력하라고 하려고 하는데, ~내장된 부분으로 다른 변수들이 확장되지 않기 때문에 read사용자가 절대 경로를 입력해야 합니다.

예: 사용자가 경로를 입력하는 경우:

read -ep "input> " dirin
  [[ -d "$dirin" ]] 

사용자가 입력했지만 /home/user/bin입력하지 않은 경우 ~/bin또는 을 반환합니다 $HOME/bin.

답변1

순진한 접근 방식은 다음과 같습니다.

eval "dirin=$dirin"

dirin=$dirin그 목적은 쉘 코드의 확장으로 평가하는 것입니다.

dirinContains 의 경우 ~/foo실제로 다음을 평가합니다.

dirin=~/foo

한계를 쉽게 알 수 있습니다. dirin포함된 경우 foo bar다음과 같습니다.

dirin=foo bar

bar따라서 해당 환경 내에서 실행됩니다 dirin=foo(그리고 모든 쉘 특수 문자에는 다른 문제가 발생합니다).

여기서 허용되는 확장(물결표, 명령 대체, 매개변수 확장, 프로세스 대체, 산술 확장, 파일 이름 확장...)을 결정하고 이러한 대체를 수동으로 수행하거나 다음을 사용하여 수행해야 합니다 eval.탈출하다~foo예를 들어 , $VAR, 등으로 제한하지 않는 한, 허용되는 문자를 제외한 모든 문자에 대해 전체 쉘 구문 분석기를 구현하는 것은 실제로 불가능합니다 ${VAR}.

여기서는 대신 특수 연산자를 사용합니다 zsh.bash

vared -cp "input> " dirin
printf "%s\n" "${(e)dirin}"

vared~이다변수 편집기, 비슷 bash하다 read -e.

(e)매개변수 내용(매개변수, 명령, 산술, 물결표 제외)의 확장을 수행하는 데 사용되는 매개변수 확장 플래그입니다.

문자열 시작 부분에서만 발생하는 물결표 확장을 수정하려면 다음을 수행합니다.

vared -cp "input> " dirin
if [[ $dirin =~ '^(~[[:alnum:]_.-]*(/|$))(.*)' ]]; then
  eval "dirin=$match[1]\${(e)match[3]}"
else
  dirin=${(e)dirin}
fi

POSIXly( bash또한 true), 물결표 및 변수(매개변수 대신) 확장을 수행하려면 다음과 같은 함수를 작성할 수 있습니다.

expand_var() {
  eval "_ev_var=\${$1}"
  _ev_outvar=
  _ev_v=${_ev_var%%/*}
  case $_ev_v in
    (?*[![:alnum:]._-]*) ;;
    ("~"*)
      eval "_ev_outvar=$_ev_v"; _ev_var=${_ev_var#"$_ev_v"}
  esac

  while :; do
    case $_ev_var in
      (*'$'*)
        _ev_outvar=$_ev_outvar${_ev_var%%"$"*}
        _ev_var=${_ev_var#*"$"}
        case $_ev_var in
          ('{'*'}'*)
            _ev_v=${_ev_var%%\}*}
            _ev_v=${_ev_v#"{"}
            case $_ev_v in
              "" | [![:alpha:]_]* | *[![:alnum:]_]*) _ev_outvar=$_ev_outvar\$ ;;
              (*) eval "_ev_outvar=\$_ev_outvar\${$_ev_v}"; _ev_var=${_ev_var#*\}};;
            esac;;
          ([[:alpha:]_]*)
            _ev_v=${_ev_var%%[![:alnum:]_]*}
            eval "_ev_outvar=\$_ev_outvar\$$_ev_v"
            _ev_var=${_ev_var#"$_ev_v"};;
          (*)
            _ev_outvar=$_ev_outvar\$
        esac;;
      (*)
        _ev_outvar=$_ev_outvar$_ev_var
        break
    esac
  done
  eval "$1=\$_ev_outvar"
}

예:

$ var='~mail/$USER'
$ expand_var var;
$ printf '%s\n' "$var"
/var/mail/stephane

대략적으로 ~${}-_.다음으로 전달하기 전에 각 문자 앞에 백슬래시를 붙일 수도 있습니다 eval.

eval "dirin=$(
  printf '%s\n' "$dirin" |
    sed 's/[^[:alnum:]~${}_.-]/\\&/g')"

(여기서 단순화하면 $dirin개행 문자는 에서 유래하므로 포함될 수 없습니다 read)

${foo#bar}예를 들어 누군가가 이를 입력하면 구문 오류가 발생하지만 최소한 단순한 오류만큼 손상되지는 않습니다 eval.

편집하다: 및 기타 POSIX 셸에서 가능한 해결책은 bash물결표와 에서와 같은 기타 확장을 분리하고 여기 문서와 함께 zsh사용하는 것입니다.eval기타 확장다음과 같은 부품:

expand_var() {
  eval "_ev_var=\${$1}"
  _ev_outvar=
  _ev_v=${_ev_var%%/*}
  case $_ev_v in
    (?*[![:alnum:]._-]*) ;;
    ("~"*)
      eval "_ev_outvar=$_ev_v"; _ev_var=${_ev_var#"$_ev_v"}
  esac
  eval "$1=\$_ev_outvar\$(cat << //unlikely//
$_ev_var
//unlikely//
)"

그러면 위와 같이 물결표, 매개변수, 산술 및 명령 확장이 가능해집니다 zsh. }

관련 정보