Programming

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

procodes 2020. 7. 15. 08:12
반응형

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


내 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가 존재할 수도 있고 존재하지 않을 수도 있습니다.


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

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


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

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

#!/bin/bash

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

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


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

eval var=$var  # Do not use this!

그 이유는 다음과 같습니다. 사용자는 우연히 (또는 목적에 따라) 유형으로 var="$(rm -rf $HOME/)"가혹한 결과를 초래할 수 있습니다.

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

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

다음 과 관련된 보안 위험없이이를 강력하게 수행 하기 위해 사전 답변 에서 나 자신을 표명합니다 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
}

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}


이건 어때요:

path=`realpath "$1"`

또는:

path=`readlink -f "$1"`

Expanding (no pun intended) on birryree's and halloleo's answers: The general approach is to use eval, but it comes with some important caveats, namely spaces and output redirection (>) in the variable. The following seems to work for me:

mypath="$1"

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

Try it with each of the following arguments:

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

Explanation

  • The ${mypath//>} strips out > characters which could clobber a file during the eval.
  • The eval echo ... is what does the actual tilde expansion
  • The double-quotes around the -e argument are for support of filenames with spaces.

Perhaps there's a more elegant solution, but this is what I was able to come up with.


I believe this is what you're looking for

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.$$
}

Example usage:

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

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

Here's my solution:

#!/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"

Just use eval correctly: with validation.

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

Here is the POSIX function equivalent of Håkon Hægland's Bash answer

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

2017-12-10 edit: add '%s' per @CharlesDuffy in the comments.


Just to extend birryree's answer for paths with spaces: You cannot use the eval command as is because it seperates evaluation by spaces. One solution is to replace spaces temporarily for the eval command:

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

This example relies of course on the assumption that mypath never contains the char sequence "_spc_".


You might find this easier to do in python.

(1) From the unix command line:

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

Results in:

/Users/someone/fred

(2) Within a bash script as a one-off - save this as test.sh:

#!/usr/bin/env bash

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

echo $thepath

Running bash ./test.sh results in:

/Users/someone/fred

(3) As a utility - save this as expanduser somewhere on your path, with execute permissions:

#!/usr/bin/env python

import sys
import os

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

This could then be used on the command line:

expanduser ~/fred

Or in a script:

#!/usr/bin/env bash

thepath=$(expanduser $1)

echo $thepath

Simplest: replace 'magic' with 'eval echo'.

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

Problem: You're going to run into issues with other variables because eval is evil. For instance:

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

Note that the issue of the injection doesn't happen on the first expansion. So if you were to simply replace magic with eval echo, you should be okay. But if you do echo $(eval echo ~), that would be susceptible to injection.

Similarly, if you do eval echo ~ instead of eval echo "~", that would count as twice expanded and therefore injection would be possible right away.


I have done this with variable parameter substitution after reading in the path using read -e (among others). So the user can tab-complete the path, and if the user enters a ~ path it gets sorted.

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

The added benefit is that if there is no tilde nothing happens to the variable, and if there is a tilde but not in the first position it is also ignored.

(I include the -i for read since I use this in a loop so the user can fix the path if there is a problem.)

참고URL : https://stackoverflow.com/questions/3963716/how-to-manually-expand-a-special-variable-ex-tilde-in-bash

반응형