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 theeval
. - 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.)
'Programming' 카테고리의 다른 글
Django에서 널을 허용하는 고유 필드 (0) | 2020.07.15 |
---|---|
django의 urls.py에서 직접 명명 된 URL 패턴으로 리디렉션 하시겠습니까? (0) | 2020.07.15 |
Linux 쉘에서 변수로 나누는 방법은 무엇입니까? (0) | 2020.07.15 |
특정 유형의 모든 이벤트 리스너 제거 (0) | 2020.07.15 |
Google Colaboratory로 데이터 가져 오기 (0) | 2020.07.15 |