Bash 변수 확장을 사용자 입력에서 직접 수행할 수 있습니까?

Bash 변수 확장을 사용자 입력에서 직접 수행할 수 있습니까?

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

프로그램 도중에 사용자에게 경로 입력을 요청하려고 하는데 ~및 기타 변수가 내장의 일부로 확장되지 않기 때문에 read사용자는 절대 경로를 입력해야 합니다.

예: 사용자가 다음에 대한 경로를 입력하는 경우:

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

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

답변1

순진한 방법은 다음과 같습니다

eval "dirin=$dirin"

그것이 하는 일은 dirin=$dirin쉘 코드로서의 확장을 평가하는 것입니다.

dirin포함을 사용하면 ~/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물결표와 변수(매개변수 아님) 확장을 수행하려면 다음과 같은 함수를 작성할 수 있습니다.

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물결표와 in zsh및 use 와 같은 기타 확장을 분리하는 것입니다.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. }

관련 정보