web-dev-qa-db-ja.com

bashで特別な変数(例:〜チルダ)を手動で展開する方法

私のbashスクリプトには、次のような値を持つ変数があります。

~/a/b/c

展開されていないチルダであることに注意してください。この変数に対してls -ltを実行すると($ VARと呼びます)、そのようなディレクトリは取得できません。この変数を実行せずにbashに解釈/展開させたいと思います。つまり、bashでevalを実行し、評価されたコマンドは実行しないようにします。これはbashで可能ですか?

これを拡張せずにスクリプトに渡すにはどうすればよいですか?引数を二重引用符で囲んで渡しました。

このコマンドを試して、意味を確認してください。

ls -lt "~"

これはまさに私がいる状況です。チルダを拡大したいです。言い換えると、これらの2つのコマンドを同一にするために、マジックを何に置き換える必要がありますか。

ls -lt ~/abc/def/ghi

そして

ls -lt $(magic "~/abc/def/ghi")

〜/ abc/def/ghiは存在する場合と存在しない場合があります。

119
madiyaan damha

StackOverflowの性質上、この答えを受け入れられないようにすることはできませんが、これを投稿してから5年の間、明らかに初歩的でかなり悪い答えよりもはるかに良い答えがありました(私は若かった、私を殺さないでください)。

このスレッドの他のソリューションは、より安全で優れたソリューションです。できれば、次の2つのいずれかを使用します。


歴史的な目的のためのオリジナルの答え(しかし、これを使用しないでください)

間違っていない場合、"~"は、リテラル文字列"~"として扱われるため、bashスクリプトによってその方法で展開されません。このようにevalを介して強制的に展開できます。

#!/bin/bash

homedir=~
eval homedir=$homedir
echo $homedir # prints home path

または、ユーザーのホームディレクトリが必要な場合は、${HOME}を使用します。

88
wkl

変数varがユーザーによって入力された場合、evalnotを使用してチルダを展開します。

_eval var=$var  # Do not use this!
_

その理由は、ユーザーが、var="$(rm -rf $HOME/)"などのタイプを誤って(または目的によって)入力して、悲惨な結果を招く可能性があるためです。

より良い(そしてより安全な)方法は、Bashパラメーター展開を使用することです:

_var="${var/#\~/$HOME}"
_
96
Håkon Hægland

evalに関連するセキュリティリスクなしでこれを堅牢に行うために、 以前の回答 から自分をPlaめる

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
}
20
Charles Duffy

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}

9
eddygeek

Birryreeとhalloleoの答えの拡張(しゃれは意図していません):一般的なアプローチは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
Noach Magedman

これはどう:

path=`realpath "$1"`

または:

path=`readlink -f "$1"`
7
Jay

これがあなたが探しているものだと思う

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
2
Orwellophile

HåkonHæglandのBashに相当するPOSIX関数を次に示します answer

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

2017-12-10編集:コメントに'%s'を@CharlesDuffyごとに追加します。

1
go2null

私のソリューションは次のとおりです。

#!/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"
1
Gino

evalを正しく使用してください:検証あり。

case $1${1%%/*} in
([!~]*|"$1"?*[!-+_.[:alnum:]]*|"") ! :;;
(*/*)  set "${1%%/*}" "${1#*/}"       ;;
(*)    set "$1" 
esac&& eval "printf '%s\n' $1${2+/\"\$2\"}"
1
mikeserv

Read -e(とりわけ)を使用してパスを読み取った後、可変パラメーター置換でこれを実行しました。そのため、ユーザーはパスをタブ補完することができ、ユーザーが〜パスを入力するとソートされます。

read -rep "Enter a path:  " -i "${testpath}" testpath 
testpath="${testpath/#~/${HOME}}" 
ls -al "${testpath}" 

追加の利点は、チルダがない場合、変数には何も起こらないことです。また、チルダがあっても最初の位置にない場合は無視されます。

(問題がある場合にユーザーがパスを修正できるように、ループでこれを使用するため、読み取り用に-iを含めます。)

0
JamesIsIn

スペースを含むパスに対する birryree の答えを拡張するだけです。スペースで評価を分離するため、evalコマンドをそのまま使用することはできません。 1つの解決策は、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が文字シーケンス"_spc_"を決して含まないという仮定に依存しています。

0
halloleo

Pythonでこれを行う方が簡単な場合があります。

(1)unixコマンドラインから:

python -c 'import os; import sys; print os.path.expanduser(sys.argv[1])' ~/fred

結果:

/Users/someone/fred

(2)bashスクリプト内で1回限り-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
0
Chris Johnson

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 "~"_を行うと、それは2回展開されたとカウントされるため、すぐに注入が可能になります。

0
Karim Alibhai