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