Как вручную развернуть специальную переменную (например: ~ тильда) в bash
У меня есть переменная в моем bash script, значение которой выглядит примерно так:
~/a/b/c
Обратите внимание, что это нерасширенная тильда. Когда я использую ls -lt для этой переменной (назовите ее $VAR), я не получаю такой каталог. Я хочу позволить bash интерпретировать/развернуть эту переменную без ее выполнения. Другими словами, я хочу, чтобы bash запускал eval, но не выполнял команду с оценкой. Возможно ли это в bash?
Как мне удалось передать это в мой script без расширения? Я передал аргумент, окружая его двойными кавычками.
Попробуйте эту команду, чтобы увидеть, что я имею в виду:
ls -lt "~"
Это именно та ситуация, в которой я нахожусь. Я хочу, чтобы тильда была расширена. Другими словами, что я должен заменить магия, чтобы сделать эти две команды одинаковыми:
ls -lt ~/abc/def/ghi
и
ls -lt $(magic "~/abc/def/ghi")
Обратите внимание, что ~/abc/def/ghi может существовать или не существовать.
Ответы
Ответ 1
Из-за характера StackOverflow я не могу просто сделать этот ответ неприемлемым, но в течение 5 лет с тех пор, как я опубликовал это, были гораздо лучшие ответы, чем мой, по общему признанию, рудиментарный и довольно плохой ответ (я был молодой, не убивай меня).
Другие решения в этой теме - более безопасные и лучшие решения. Предпочтительно, я бы пошел с любым из этих двух:
Оригинальный ответ для исторических целей (но, пожалуйста, не используйте это)
Если я не ошибаюсь, "~"
не будет расширяться bash script таким образом, потому что он рассматривается как буквальная строка "~"
. Вы можете принудительно выполнить расширение с помощью eval
следующим образом.
#!/bin/bash
homedir=~
eval homedir=$homedir
echo $homedir # prints home path
В качестве альтернативы просто используйте ${HOME}
, если вы хотите, чтобы пользовательский домашний каталог.
Ответ 2
Если переменная var
вводится пользователем, eval
следует использовать не, чтобы развернуть тильду, используя
eval var=$var # Do not use this!
Причина в том, что пользователь может случайно (или по назначению) указать тип var="$(rm -rf $HOME/)"
с возможными катастрофическими последствиями.
Лучше (и безопаснее) использовать расширение Bash:
var="${var/#\~/$HOME}"
Ответ 3
Плагируя себя от предварительного ответа, чтобы сделать это без риска безопасности, связанного с 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 _ _ _ _ _ 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
Безопасный способ использования eval - "$(printf "~/%q" "$dangerous_path")"
. Обратите внимание, что это bash specific.
#!/bin/bash
relativepath=a/b/c
eval homedir="$(printf "~/%q" "$relativepath")"
echo $homedir # prints home path
Подробнее см. этот вопрос
Также обратите внимание, что в zsh это будет так же просто, как echo ${~dangerous_path}
Ответ 5
Как насчет этого:
path=`realpath "$1"`
Или:
path=`readlink -f "$1"`
Ответ 6
Расширение (без каламбура) на бирильные и холлолоу ответы: общий подход заключается в использовании 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
предназначены для поддержки имен файлов с пробелами.
Возможно, там более элегантное решение, но это то, что я смог придумать.
Ответ 7
Я считаю, что это то, что вы ищете
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
Ответ 8
Здесь мое решение:
#!/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"
Ответ 9
Просто используйте eval
правильно: с проверкой.
case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*) set "${1%%/*}" "${1#*/}" ;;
(*) set "$1"
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
Ответ 10
Вот функция POSIX, эквивалентная Håkon Hægland Bash answer
expand_tilde() {
tilde_less="${1#\~/}"
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
printf '%s' "$tilde_less"
}
2017-12-10 изменить: добавить '%s'
за @CharlesDuffy в комментариях.
Ответ 11
Просто чтобы продлить 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
Этот пример полагается, конечно, на предположение, что mypath
никогда не содержит последовательность char "_spc_"
.
Ответ 12
Вы можете найти это проще в python.
(1) Из командной строки unix:
python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred
Результаты в:
/Users/someone/fred
(2) В пределах bash script как одноразовый - сохраните это как 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
Или в script:
#!/usr/bin/env bash
thepath=$(expanduser $1)
echo $thepath
Ответ 13
Простейший: замените "магия" на "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 "~"
, это будет считаться удвоенным и, следовательно, инъекция будет возможна сразу.
Ответ 14
Я сделал это с подстановкой переменных параметров после чтения в пути с использованием чтения -e (среди прочих). Таким образом, пользователь может завершить путь путем табуляции, и если пользователь вводит ~ путь, он сортируется.
read -rep "Enter a path: " -i "${testpath}" testpath
testpath="${testpath/#~/${HOME}}"
ls -al "${testpath}"
Дополнительным преимуществом является то, что при отсутствии тильды с переменной ничего не происходит, и если тильда есть, но не в первой позиции, она также игнорируется.
(Я включаю -i для чтения, поскольку я использую его в цикле, чтобы пользователь мог исправить путь в случае возникновения проблемы.)