bash에서 특수 변수를 수동으로 확장하는 방법 (예 : ~ 물결표)


135

내 bash 스크립트에 값이 다음과 같은 변수가 있습니다.

~/a/b/c

확장되지 않은 물결표입니다. 이 변수에 대해 ls -lt를 수행하면 ($ VAR이라고 함) 그러한 디렉토리가 없습니다. bash 가이 변수를 실행하지 않고 해석 / 확장하도록하고 싶습니다. 즉, bash가 eval을 실행하고 평가 된 명령은 실행하지 않기를 원합니다. bash에서 가능합니까?

확장하지 않고 스크립트에 어떻게 전달 했습니까? 나는 큰 따옴표로 둘러싼 논쟁을 통과시켰다.

이 명령을 사용하여 무슨 뜻인지 확인하십시오.

ls -lt "~"

이것이 바로 내가 처한 상황입니다. 물결표가 확장되기를 원합니다. 다시 말해,이 두 명령을 동일하게하기 위해 마법을 대체해야하는 것은 무엇입니까?

ls -lt ~/abc/def/ghi

ls -lt $(magic "~/abc/def/ghi")

~ / abc / def / ghi가 존재할 수도 있고 존재하지 않을 수도 있습니다.


4
따옴표로 묶인 Tilde 확장 도 도움 이 될 수 있습니다. 대부분은 아니지만 완전히 사용하지 않습니다 eval.
Jonathan Leffler 2014

2
변수에 확장되지 않은 물결표가 어떻게 할당 되었습니까? 어쩌면 필요한 것은 따옴표 밖에 물결표로 변수를 할당하는 것입니다. foo=~/"$filepath"또는foo="$HOME/$filepath"
차드 스키터

dir="$(readlink -f "$dir")"
Jack Wasey

답변:


98

StackOverflow의 특성으로 인해이 답변을 받아 들일 수는 없지만이 게시물을 게시 한 후 5 년 동안 저의 초보적이고 꽤 나쁜 답변보다 훨씬 더 나은 답변이있었습니다 (어려서 죽이지 마십시오) 나를).

이 스레드의 다른 솔루션은 더 안전하고 더 나은 솔루션입니다. 바람직하게는 다음 두 가지 중 하나를 선택합니다.


역사적인 목적을위한 독창적 인 답변 (그러나 이것을 사용하지 마십시오)

내가 실수 "~"하지 않으면 리터럴 문자열로 취급되기 때문에 bash 스크립트로 확장되지 않습니다 "~". eval이런 식으로 확장을 강제 할 수 있습니다 .

#!/bin/bash

homedir=~
eval homedir=$homedir
echo $homedir # prints home path

또는 ${HOME}사용자의 홈 디렉토리를 원할 경우 사용 하십시오.


3
변수에 공백이있을 때 수정 했습니까?
Hugo

34
내가 찾은 ${HOME}가장 매력적인. 이것을 기본 추천으로 삼지 말아야 할 이유가 있습니까? 어쨌든 감사합니다!
Sage

1
+1-~ $ some_other_user를 확장해야했고 현재 사용자 홈이 필요하지 않기 때문에 $ HOME이 작동하지 않으면 eval이 제대로 작동합니다.
olivecoder

11
사용하는 eval것은 끔찍한 제안입니다. 변수 값에 쉘 메타 문자가 포함되어 있으면 모든 종류의 문제가 발생합니다.
user2719058

1
그 당시에는 의견을 마무리 할 수 ​​없었으며 나중에 편집 할 수 없었습니다. 그래서 당시의 특정 상황에서 도움이 된이 솔루션에 대해 감사드립니다 (다시 @birryree에게 감사드립니다). 알려준 찰스 감사합니다.
olivecoder

114

var사용자가 변수 를 입력 한 경우 다음을 사용하여 물결표를 확장하는 데 사용 eval해서는 안됩니다 .

eval var=$var  # Do not use this!

그 이유는 다음과 같습니다. 사용자는 우연히 (또는 목적에 따라) 유형으로 예를 들어 var="$(rm -rf $HOME/)"재앙이 발생할 수 있습니다.

더 좋고 안전한 방법은 Bash 매개 변수 확장을 사용하는 것입니다.

var="${var/#\~/$HOME}"

8
~ / 대신 ~ userName /을 어떻게 변경할 수 있습니까?
aspergillusOryzae

3
의 목적은 무엇인가 #에가 "${var/#\~/$HOME}"?
Jahid

3
@Jahid 매뉴얼에 설명되어 있습니다 . 시작시에만 물결표가 일치하도록합니다 $var.
Håkon Hægland 2016 년

1
감사. (1) 왜 \~탈출 해야 ~합니까? (2) 귀하의 회신 ~은의 첫 문자 인 것으로 가정합니다 $var. 우리는 어떻게 공백을 무시할 수 $var있습니까?
Tim

1
의견을 보내 주셔서 감사합니다. 그렇습니다. 인용 부호가없는 문자열의 첫 문자가 아니거나 인용 부호가없는 문자열을 따르는 경우가 아니면 물결표를 이스케이프 처리하지 않아도됩니다 :. 더 많은 정보를 워드 프로세서 . 선행 공백을 제거하려면 Bash 변수에서 공백을 자르는 방법을
Håkon Hægland

24

다음 과 관련된 보안 위험없이이를 강력하게 수행 하기 위해 사전 답변 에서 나 자신을 표명합니다 eval.

expandPath() {
  local path
  local -a pathElements resultPathElements
  IFS=':' read -r -a pathElements <<<"$1"
  : "${pathElements[@]}"
  for path in "${pathElements[@]}"; do
    : "$path"
    case $path in
      "~+"/*)
        path=$PWD/${path#"~+/"}
        ;;
      "~-"/*)
        path=$OLDPWD/${path#"~-/"}
        ;;
      "~"/*)
        path=$HOME/${path#"~/"}
        ;;
      "~"*)
        username=${path%%/*}
        username=${username#"~"}
        IFS=: read -r _ _ _ _ _ homedir _ < <(getent passwd "$username")
        if [[ $path = */* ]]; then
          path=${homedir}/${path#*/}
        else
          path=$homedir
        fi
        ;;
    esac
    resultPathElements+=( "$path" )
  done
  local result
  printf -v result '%s:' "${resultPathElements[@]}"
  printf '%s\n' "${result%:}"
}

...로 사용 ...

path=$(expandPath '~/hello')

또는 eval신중하게 사용하는 간단한 접근 방식 :

expandPath() {
  case $1 in
    ~[+-]*)
      local content content_q
      printf -v content_q '%q' "${1:2}"
      eval "content=${1:0:2}${content_q}"
      printf '%s\n' "$content"
      ;;
    ~*)
      local content content_q
      printf -v content_q '%q' "${1:1}"
      eval "content=~${content_q}"
      printf '%s\n' "$content"
      ;;
    *)
      printf '%s\n' "$1"
      ;;
  esac
}

4
코드를 보면 대포를 사용하여 모기를 죽인 것처럼 보입니다. 거기 있어 훨씬 더 간단한 방법이 ..
지노

2
@Gino, 더 간단한 방법이 있습니다. 문제는 더 간단한 방법이 안전한지 여부입니다.
Charles Duffy

2
@Gino, ... 나는 일이 사용할 수있는 가정 printf %q의 모든하지만 물결표를 탈출하고, 다음 사용 eval위험없이.
Charles Duffy

1
@Gino, ... 그리고 구현되었습니다.
Charles Duffy

3
안전합니다.하지만 매우 불완전합니다. 내 코드는 재미로 복잡하지 않습니다. 물결표 확장으로 수행되는 실제 작업이 복잡하기 때문에 복잡합니다.
Charles Duffy

10

eval을 사용하는 안전한 방법은 "$(printf "~/%q" "$dangerous_path")"입니다. 배쉬 전용입니다.

#!/bin/bash

relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path

자세한 내용은 이 질문 을 참조하십시오

또한 zsh에서 이것은 다음과 같이 간단합니다. echo ${~dangerous_path}


echo ${~root}zsh (mac os x)에 대한 출력 없음
Orwellophile

export test="~root/a b"; echo ${~test}
Gyscos

9

이건 어때요:

path=`realpath "$1"`

또는:

path=`readlink -f "$1"`

멋지지만 realpath는 내 Mac에 존재하지 않습니다. 그리고 당신은 path = $ (realpath "$ 1")을 써야합니다
Hugo

안녕하세요 @ Hugo. realpathC에서 고유 한 명령을 컴파일 할 수 있습니다 . 예를 들어 다음 명령 줄에서 bashgccrealpath.exe사용하여 실행 파일 을 생성 할 수 있습니다 . 건배gcc -o realpath.exe -x c - <<< $'#include <stdlib.h> \n int main(int c,char**v){char p[9999]; realpath(v[1],p); puts(p);}'
올리버

@Quuxplusone은 적어도 리눅스에서는 사실이 아닙니다 : realpath ~->/home/myhome
blueFast

iv'e는 맥에 양조와 함께 사용
nhed

1
@dangonfast 물결표를 따옴표로 설정하면 작동하지 않습니다 <workingdir>/~. 결과는 입니다.
머피

7

Birryrees와 Halloleo의 답변에 대한 확장 (장어 의도 없음) : 일반적인 접근 방식은을 사용하는 eval것이지만 >변수에 공백과 출력 리디렉션 ( ) 과 같은 몇 가지주의 사항 이 있습니다. 다음은 저에게 효과적입니다.

mypath="$1"

if [ -e "`eval echo ${mypath//>}`" ]; then
    echo "FOUND $mypath"
else
    echo "$mypath NOT FOUND"
fi

다음 각 인수로 시도하십시오.

'~'
'~/existing_file'
'~/existing file with spaces'
'~/nonexistant_file'
'~/nonexistant file with spaces'
'~/string containing > redirection'
'~/string containing > redirection > again and >> again'

설명

  • ${mypath//>}떼어 내고 >동안 파일을 소지품 수있는 자 eval.
  • eval echo ...실제 틸드 확장을하는 일이다
  • -e인수 주위의 큰 따옴표 는 공백이있는 파일 이름을 지원하기위한 것입니다.

아마도 더 우아한 해결책이 있지만 이것이 내가 생각해 낸 것입니다.


3
이름이 포함 된 동작을 살펴볼 수 있습니다 $(rm -rf .).
Charles Duffy

1
>그러나 실제로 문자 가 포함 된 경로에서 중단되지 않습니까?
Radon Rosborough

2

나는 이것이 당신이 찾고있는 것이라고 믿습니다.

magic() { # returns unexpanded tilde express on invalid user
    local _safe_path; printf -v _safe_path "%q" "$1"
    eval "ln -sf ${_safe_path#\\} /tmp/realpath.$$"
    readlink /tmp/realpath.$$
    rm -f /tmp/realpath.$$
}

사용법 예 :

$ magic ~nobody/would/look/here
/var/empty/would/look/here

$ magic ~invalid/this/will/not/expand
~invalid/this/will/not/expand

나는 선도적 인 물결표를 피 printf %q 하지 못한다는 사실에 놀랐습니다 . 이것이 명시된 목적으로 실패하는 상황이기 때문에 버그로 신고하는 것이 거의 유혹적입니다. 그러나 중간에 좋은 전화!
Charles Duffy

1
실제로이 버그는 3.2.57과 4.3.18 사이에서 수정되었으므로이 코드는 더 이상 작동하지 않습니다.
Charles Duffy

1
좋은 점은 선행하는 \를 제거하도록 코드로 조정했기 때문에 모든 고정되고 작동했습니다. :) 인수를 인용하지 않고 테스트하고 있었으므로 함수를 호출하기 전에 확장되었습니다.
Orwellophile

1

내 해결책은 다음과 같습니다.

#!/bin/bash


expandTilde()
{
    local tilde_re='^(~[A-Za-z0-9_.-]*)(.*)'
    local path="$*"
    local pathSuffix=

    if [[ $path =~ $tilde_re ]]
    then
        # only use eval on the ~username portion !
        path=$(eval echo ${BASH_REMATCH[1]})
        pathSuffix=${BASH_REMATCH[2]}
    fi

    echo "${path}${pathSuffix}"
}



result=$(expandTilde "$1")

echo "Result = $result"

또한 의존 echo하는 expandTilde -n것은 예상대로 작동하지 않으며 백 슬래시를 포함하는 파일 이름의 동작은 POSIX에 의해 정의되지 않습니다. 참조 pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html
찰스 더피

잘 잡았습니다. 나는 일반적으로 단일 사용자 컴퓨터를 사용하므로 그 경우를 처리하지 않았다. 그러나 다른 사용자를 위해 / etc / passwd 파일을 grepping 하여이 다른 경우를 처리하도록 기능을 쉽게 향상시킬 수 있다고 생각합니다. 나는 다른 사람을위한 운동으로 남겨 둘 것이다 :).
지노

나는 당신이 너무 복잡하다고 생각하는 답변으로 이미 그 운동을하고 (OLDPWD 사례와 다른 사람들을 처리했습니다). :)
Charles Duffy

실제로, 나는 다른 사용자 사례를 처리 해야하는 상당히 간단한 한 줄 솔루션을 발견했습니다. path = $ (eval echo $ orgPath)
Gino

1
참고 : 방금 솔루션을 업데이트하여 ~ username을 올바르게 처리 할 수있게되었습니다. 또한 상당히 안전해야합니다. '/ tmp / $ (rm -rf / *)'를 인수로 입력하더라도 정상적으로 처리해야합니다.
지노

1

eval유효성 검사와 함께 올바르게 사용하십시오 .

case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*)  set "${1%%/*}" "${1#*/}"       ;;
(*)    set "$1" 
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"

이것은 아마도 안전합니다-그것이 실패한 경우를 찾지 못했습니다. 즉, 우리가 eval을 "올바로"사용하는 것에 대해 이야기한다면 Orwellophile의 대답이 더 나은 방법을 따른다고 주장 할 것입니다. 나는 셸이 printf %q버그를 가지지 않는 손으로 작성한 유효성 검사 코드를 신뢰하는 것보다 더 안전하게 도망 칠 수 있다고 믿습니다. .
Charles Duffy

@Charles Duffy-바보입니다. 쉘에는 % q가 없을 수도 printf있으며 $PATH'd 명령입니다.
mikeserv

1
이 질문에 태그가 bash없습니까? 그렇다면 printf기본 제공 %q되며 존재하는 것으로 보장됩니다.
Charles Duffy

@ 찰스 더피-어떤 버전?
mikeserv

1
@Charles Duffy-그건 .. 꽤 일찍입니다. 하지만 난 아직도 당신이 더 당신이 바로 눈 앞에 코드 사용 필자 것보다 % Q 인수를 믿을 거라고는 이상한 생각 bash을 알고 전에 충분히 하지 를 신뢰. 시도 :x=$(printf \\1); [ -n "$x" ] || echo but its not null!
mikeserv

1

Håkon Hægland의 Bash 답변 과 동등한 POSIX 함수는 다음과 같습니다.

expand_tilde() {
    tilde_less="${1#\~/}"
    [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
    printf '%s' "$tilde_less"
}

2017-12-10 편집 : '%s'의견에 @CharlesDuffy 당 추가하십시오 .


1
printf '%s\n' "$tilde_less", 혹시? 그렇지 않으면 확장중인 파일 이름에 백 슬래시 %s, 또는 기타 의미있는 구문이 포함되어 있으면 잘못 작동합니다 printf. 그러나 그 외에는 올바른 대답입니다. 올바른 (bash / ksh 확장명을 다룰 필요가없는 경우) 확실하고 안전하며 (와 함께 뭉치지 않음 eval) 간결합니다.
Charles Duffy

1

왜 getent를 사용하여 사용자의 홈 디렉토리를 얻는 지 직접 조사해 보지 않겠습니까?

$ getent passwd mike | cut -d: -f6
/users/mike

0

공백이있는 경로에 대한 birryree 의 대답 을 확장 하려면 공백으로 eval평가를 분리하기 때문에 명령을 그대로 사용할 수 없습니다 . 한 가지 해결책은 eval 명령의 공백을 임시로 바꾸는 것입니다.

mypath="~/a/b/c/Something With Spaces"
expandedpath=${mypath// /_spc_}    # replace spaces 
eval expandedpath=${expandedpath}  # put spaces back
expandedpath=${expandedpath//_spc_/ }
echo "$expandedpath"    # prints e.g. /Users/fred/a/b/c/Something With Spaces"
ls -lt "$expandedpath"  # outputs dir content

이 예제는 물론 mypathchar 시퀀스를 포함하지 않는 가정에 의존 합니다 "_spc_".


1
탭, 줄 바꿈 또는 IFS의 다른 항목에서는 작동하지 않으며 다음과 같은 경로와 같은 메타 문자에 대해서는 보안을 제공하지 않습니다.$(rm -rf .)
Charles Duffy

0

파이썬에서 더 쉽게 할 수 있습니다.

(1) 유닉스 커맨드 라인에서 :

python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred

결과 :

/Users/someone/fred

(2) bash 스크립트 내에서 일회용으로-이것을 다음과 같이 저장하십시오 test.sh:

#!/usr/bin/env bash

thepath=$(python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' $1)

echo $thepath

실행 bash ./test.sh결과 :

/Users/someone/fred

(3) 유틸리티로서- expanduser실행 권한을 사용하여 경로의 어딘가에 저장하십시오 .

#!/usr/bin/env python

import sys
import os

print os.path.expanduser(sys.argv[1])

그런 다음 명령 행에서 사용할 수 있습니다.

expanduser ~/fred

또는 스크립트에서 :

#!/usr/bin/env bash

thepath=$(expanduser $1)

echo $thepath

아니면 "/ home / fred"를 반환하여 파이썬에 '~'만 전달하는 것은 어떻습니까?
Tom Russell

1
따옴표가 필요합니다. echo $thepath버기입니다. 할 필요 echo "$thepath"하거나, 덜 드문 경우를 해결하기 위해 (globs와 그들을 확장 갖는 이름을 탭 또는 공백의 실행과 이름이 하나의 공간으로 변환되는) printf '%s\n' "$thepath". (너무 흔한 것들을 해결하기 위해 즉, 파일 이름 -n또는 파일을 XSI 호환 시스템의 백 슬래시 리터럴). 마찬가지로,thepath=$(expanduser "$1")
찰스 더피

백 슬래시 리터럴에 대한 의미를 이해하려면 pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html을 참조하십시오. POSIX를 사용하면 echo인수에 백 슬래시가 포함 된 경우 완전히 구현 정의 된 방식으로 동작 할 수 있습니다. POSIX에 대한 선택적 XSI 확장 은 그러한 이름에 대한 기본 (필수 -e또는 -E필요 하지 않은 ) 확장 동작을 요구합니다.
Charles Duffy

0

가장 간단한 : 'magic'을 'eval echo'로 대체하십시오.

$ eval echo "~"
/whatever/the/f/the/home/directory/is

문제 : eval은 악하기 때문에 다른 변수에 문제가 생길 수 있습니다. 예를 들어 :

$ # home is /Users/Hacker$(s)
$ s="echo SCARY COMMAND"
$ eval echo $(eval echo "~")
/Users/HackerSCARY COMMAND

주입 문제는 첫 번째 확장에서 발생하지 않습니다. 당신은 단순히 대체한다면 그래서 magic함께 eval echo, 당신은 괜찮을 것 같네요. 그러나 그렇게하면 echo $(eval echo ~)주사하기 쉽다.

마찬가지로, eval echo ~대신에 eval echo "~"두 번 확장 한 것으로 간주되므로 즉시 주입이 가능합니다.


1
말한 내용과 달리이 코드는 안전하지 않습니다 . 예를 들어 test s='echo; EVIL_COMMAND'입니다. ( EVIL_COMMAND컴퓨터에 존재 하지 않기 때문에 실패 할 것입니다 . 그러나 그 명령이 rm -r ~예를 들어, 홈 디렉토리를 삭제했을 것입니다.)
Konrad Rudolph

0

read -e (다른 것들 중에서)를 사용하여 경로를 읽은 후 변수 매개 변수 대체로이 작업을 수행했습니다. 따라서 사용자는 경로를 탭 완성하고 사용자가 ~ 경로를 입력하면 정렬됩니다.

read -rep "Enter a path:  " -i "${testpath}" testpath 
testpath="${testpath/#~/${HOME}}" 
ls -al "${testpath}" 

물결표가없는 경우 변수에 아무런 변화가 없으면 물결표가 있지만 첫 번째 위치에는없는 경우에도 무시됩니다.

(루프에서 이것을 사용하기 때문에 읽을 수 있도록 -i를 포함하므로 문제가있는 경우 사용자가 경로를 수정할 수 있습니다.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.