Как вручную расширить специальную переменную (Ex: ~ Tilde) в Bash
-
09-10-2019 - |
Вопрос
У меня есть переменная в моем скрипте 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 могут или не могут существовать.
Решение
Из-за природы стокового потока я не могу просто сделать этот ответ неприемлемым, но в прошествии 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"`
Расширение (без каламбура) на ответах Биррири и Халлолео: общий подход - использовать 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
Аргумент предназначены для поддержки имена файлов пробелами.
Возможно, есть более элегантное решение, но это то, что я смог придумать.
Я верю, что это то, что вы ищете
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
Вот мое решение:
#!/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"
Просто использовать eval
Правильно: с проверкой.
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
Вот функция POSIX, эквивалентна Bash Håkon Hægland отвечать
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
2017-12-10 Редактировать: добавить '%s'
за @charlesduffy в комментариях.
Просто чтобы продлить бирририответ на пути с пробелами: вы не можете использовать 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
Этот пример зависит от курса на предположении, что mypath
Никогда не содержит последовательность CHAR "_spc_"
.
Вы можете найти это легче сделать в Python.
(1) из командной строки UNIX:
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
Простейший: замените «магию» с «EVECHO».
$ 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 "~"
, это будет считаться в два раза в два раза, и поэтому инъекция будет возможна сразу.
Я сделал это с переменным замещением параметров после прочтения на пути с помощью чтения (среди прочего). Таким образом, пользователь может заполнить путь, и если пользователь входит в путь, он сортируется.
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
Дополнительное преимущество заключается в том, что если нет Tilde, ничего не происходит с переменной, и если есть тильда, но не в первой позиции, также игнорируется.
(Я включаю --i для чтения, так как я использую это в цикле, поэтому пользователь может исправить путь, если есть проблема.)