Как вручную расширить специальную переменную (Ex: ~ Tilde) в Bash

StackOverflow https://stackoverflow.com/questions/3963716

Вопрос

У меня есть переменная в моем скрипте 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 для чтения, так как я использую это в цикле, поэтому пользователь может исправить путь, если есть проблема.)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top