Как вручную развернуть специальную переменную (например: ~ тильда) в 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 лет с тех пор, как я опубликовал это, были гораздо лучшие ответы, чем мой, по общему признанию, рудиментарный и довольно плохой ответ (я был молод, не убивал меня).

    Другие решения в этой теме – более безопасные и лучшие решения. Предпочтительно, я бы пошел с любым из этих двух:

    • Решение Шарли Даффи
    • Решение Håkon Hægland

    Оригинальный ответ для исторических целей (но, пожалуйста, не используйте это)

    Если я не ошибаюсь, "~" не будет расширяться скриптом 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/)" тип 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 _ _ _ _ _ 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")" . Обратите внимание, что это bash.

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

    См. Этот вопрос для подробностей

    Кроме того, обратите внимание, что в zsh это будет так же просто, как echo ${~dangerous_path}

    Расширение (без каламбура) на ответы birryree’s и halloleo: общий подход заключается в использовании eval , но он содержит некоторые важные оговорки, а именно пробелы и redirect вывода ( > ) в переменной. Для меня, похоже, работает:

     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 предназначены для поддержки имен файлов с пробелами.

    Возможно, есть более элегантное решение, но это то, что я смог придумать.

    Как насчет этого:

     path=`realpath "$1"` 

    Или:

     path=`readlink -f "$1"` 

    Я считаю, что это то, что вы ищете

     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, эквивалентная ответу Hash Hægon Hægland’s Bash

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

    2017-12-10 edit: добавьте '%s' за @CharlesDuffy в комментариях.

    Просто чтобы расширить ответ 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 eg /Users/fred/a/b/c/Something With Spaces" ls -lt "$expandedpath" # outputs dir content 

    Этот пример, конечно, полагается на предположение, что mypath никогда не содержит последовательность символов "_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 

    Simplest : замените «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 "~" , это будет считаться удвоенным, и поэтому инъекция будет возможна сразу.

    Давайте будем гением компьютера.