このように_$PATH
_を設定するbash関数があります-
_assign-path()
{
str=$1
# if the $PATH is empty, assign it directly.
if [ -z $PATH ]; then
PATH=$str;
# if the $PATH does not contain the substring, append it with ':'.
Elif [[ $PATH != *$str* ]]; then
PATH=$PATH:$str;
fi
}
_
しかし問題は、変数ごとに異なる関数を記述しなければならないことです(たとえば、assign-classpath()
などの_$CLASSPATH
_の別の関数)。参照でアクセスできるようにbash関数に引数を渡す方法が見つかりませんでした。
こんなものがあったらいいのに…
_assign( bigstr, substr )
{
if [ -z bigstr ]; then
bigstr=substr;
Elif [[ bigstr != *str* ]]; then
bigstr=bigstr:substr;
fi
}
_
上記のようなものをbashで実現するにはどうすればよいですか?
bash
では、${!varname}
を使用して、別のコンテンツによって参照される変数を展開できます。例えば:
$ var=hello
$ foo () { echo "${!1}"; }
$ foo var
hello
Manページから:
${!prefix*}
${!prefix@}
Names matching prefix. Expands to the names of variables whose names
begin with prefix, separated by the first character of the IFS special
variable. When @ is used and the expansion appears within double quotes,
each variable name expands to a separate Word.
また、内容によって参照される変数を設定するために(eval
の危険なしで)、declare
を使用できます。例えば:
$ var=target
$ declare "$var=hello"
$ echo "$target"
hello
したがって、次のように関数を記述できます(関数でdeclare
を使用する場合、-g
を指定する必要があります。そうしないと、変数がローカルになります)。
shopt -s extglob
assign()
{
target=$1
bigstr=${!1}
substr=$2
if [ -z "$bigstr" ]; then
declare -g -- "$target=$substr"
Elif [[ $bigstr != @(|*:)$substr@(|:*) ]]; then
declare -g -- "$target=$bigstr:$substr"
fi
}
そしてそれを次のように使用します:
assign PATH /path/to/binaries
substr
がbigstr
のコロンで区切られたメンバーの1つの部分文字列であるが、それ自体のメンバーではない場合、追加されないというバグも修正しました。たとえば、これにより、すでに/bin
が含まれているPATH
変数に/usr/bin
を追加できます。 extglob
セットを使用して、文字列の先頭/末尾またはコロンのいずれかと一致し、それ以外のすべてと一致します。 extglob
がない場合、代替案は次のようになります。
[[ $bigstr != $substr && $bigstr != *:$substr &&
$bigstr != $substr:* && $bigstr != *:$substr:* ]]
Bash 4.3の新機能は-n
オプションdeclare
&local
:
func() {
local -n ref="$1"
ref="hello, world"
}
var='goodbye world'
func var
echo "$var"
それはhello, world
。
eval
を使用してパラメーターを設定できます。このコマンドの説明は here にあります。次のeval
の使い方は間違っています:
wrong(){ eval $ 1 = $ 2 }
追加の評価に関しては、eval
doesを使用する必要があります
assign(){ eval $ 1 = '$ 2' }
これらの関数を使用した結果を確認します。
$ X1 = '$ X2' $ X2 = '$ X3' $ X3 = 'xxx' $ $ echo:$ X1: :$ X2: $ echo:$ X2: :$ X3: $ echo:$ X3: :xxx: $ $間違ったY $ X1 $ echo:$ Y: :$ X3: $ $割り当てY $ X1 $ echo:$ Y: :$ X2: $ $ Yに「ハローワールド」を割り当てます $ echo:$ Y : :hallo world: $#以下は予期しない可能性があります $割り当てZ $ Y $ echo ":$ Z:" :hallo: $#2番目の引数が変数の場合は引用符で囲む必要があります $はZ "$ Y" $ echo ":$ Z:" [.____を割り当てます。]:ハローワールド:
しかし、eval
を使用しなくても目標を達成できます。私はこの方が簡単です。
次の関数は正しい方法で置換を行います(私は願っています)
augment(){ local CURRENT = $ 1 local AUGMENT = $ 2 local NEW if [[-z $ CURRENT]];次に NEW = $ AUGMENT Elif [[! (($ CURRENT = $ AUGMENT)||($ CURRENT = $ AUGMENT:*)||\ ($ CURRENT = *:$ AUGMENT)||($ CURRENT = *:$ AUGMENT:*)) ]];次に NEW = $ CURRENT:$ AUGMENT else NEW = $ CURRENT fi echo "$ NEW" }
次の出力を確認してください
augment/usr/bin /bin /usr/bin:/bin augment/usr/bin:/ bin /bin / usr/bin:/ bin augment/usr/bin:/ bin:/ usr/local/bin /bin /usr/bin:/bin:/usr/local /bin augment/bin:/ usr/bin /bin /bin:/usr/bin augment/bin/bin /bin augment/usr/bin:/bin /usr/bin::/bin augment/usr/bin:/ bin:/bin /usr/bin:/bin: augment/usr/bin:/ bin:/ usr/local/bin:/bin /usr/bin:/bin:/usr/local/bin: augment/bin:/ usr/bin:/bin /bin:/usr/bin: augment/bin:/bin /bin: augment:/ bin ::/bin augment "/ usr lib" "/ usr bin" /usr lib:/ usr bin augment "/ usr lib:/ usr bin" "/ usr bin" /usr lib:/ usr bin
これで、augment
関数を次のように使用して変数を設定できます。
PATH = `augment PATH /bin` CLASSPATH=`augment CLASSPATH /bin` LD_LIBRARY_PATH=`augment LD_LIBRARY_PATH /usr/lib`
いくつかのトリックを使用すると、名前付きパラメーターを配列とともに関数に渡すことができます(bash 3および4でテスト済み)。
私が開発したメソッドを使用すると、次のような関数に渡されるパラメーターにアクセスできます。
testPassingParams() {
@var hello
l=4 @array anArrayWithFourElements
l=2 @array anotherArrayWithTwo
@var anotherSingle
@reference table # references only work in bash >=4.3
@params anArrayOfVariedSize
test "$hello" = "$1" && echo correct
#
test "${anArrayWithFourElements[0]}" = "$2" && echo correct
test "${anArrayWithFourElements[1]}" = "$3" && echo correct
test "${anArrayWithFourElements[2]}" = "$4" && echo correct
# etc...
#
test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
#
test "$anotherSingle" = "$8" && echo correct
#
test "${table[test]}" = "works"
table[inside]="adding a new value"
#
# I'm using * just in this example:
test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}
fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"
testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."
test "${assocArray[inside]}" = "adding a new value"
言い換えると、名前でパラメーターを呼び出すことができる(より読みやすいコアを構成する)だけでなく、実際に配列(および変数への参照-この機能はbash 4.3でのみ機能します)を渡すことができます!さらに、マップされた変数はすべて$ 1(およびその他)と同じようにローカルスコープ内にあります。
これを機能させるコードは非常に軽く、bash 3とbash 4の両方で機能します(これらは私がテストした唯一のバージョンです)。このようなbashでの開発をより簡単に簡単にするトリックに興味がある場合は、私の Bash Infinity Framework を参照してください。以下のコードはその目的のために開発されました。
Function.AssignParamLocally() {
local commandWithArgs=( $1 )
local command="${commandWithArgs[0]}"
shift
if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
then
paramNo+=-1
return 0
fi
if [[ "$command" != "local" ]]
then
assignNormalCodeStarted=true
fi
local varDeclaration="${commandWithArgs[1]}"
if [[ $varDeclaration == '-n' ]]
then
varDeclaration="${commandWithArgs[2]}"
fi
local varName="${varDeclaration%%=*}"
# var value is only important if making an object later on from it
local varValue="${varDeclaration#*=}"
if [[ ! -z $assignVarType ]]
then
local previousParamNo=$(expr $paramNo - 1)
if [[ "$assignVarType" == "array" ]]
then
# passing array:
execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
eval "$execute"
paramNo+=$(expr $assignArrLength - 1)
unset assignArrLength
Elif [[ "$assignVarType" == "params" ]]
then
execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
eval "$execute"
Elif [[ "$assignVarType" == "reference" ]]
then
execute="$assignVarName=\"\$$previousParamNo\""
eval "$execute"
Elif [[ ! -z "${!previousParamNo}" ]]
then
execute="$assignVarName=\"\$$previousParamNo\""
eval "$execute"
fi
fi
assignVarType="$__capture_type"
assignVarName="$varName"
assignArrLength="$__capture_arrLength"
}
Function.CaptureParams() {
__capture_type="$_type"
__capture_arrLength="$l"
}
alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'
assign ()
{
if [ -z ${!1} ]; then
eval $1=$2
else
if [[ ${!1} != *$2* ]]; then
eval $1=${!1}:$2
fi
fi
}
$ echo =$x=
==
$ assign x y
$ echo =$x=
=y=
$ assign x y
$ echo =$x=
=y=
$ assign x z
$ echo =$x=
=y:z=
これは合いますか?
名前付き引数は、Bashの構文が設計された方法ではありません。 Bashは、Bourne Shellの反復的な改善として設計されました。そのため、2つのシェル間で特定のことができる限り機能するようにする必要があります。つまり、全体的にスクリプトを記述しやすくすることはmeantではなく、Bourne環境からbash
にスクリプトを取得する場合に、Bourneよりも優れていることを意味します。できるだけ簡単です。多くのシェルがまだボーンを事実上の標準として扱っているので、それは簡単なことではありません。人々は、この互換性のためにBourne互換になるようにスクリプトを作成しているため、ニーズは依然として有効であり、変更される可能性はほとんどありません。
可能であれば、別のシェルスクリプト(python
など)を完全に見た方がよいでしょう。言語の制限に直面している場合は、新しい言語の使用を開始する必要があります。
標準のsh
構文(bash
だけでなくbash
でも機能します)を使用すると、次のことができます。
assign() {
eval '
case :${'"$1"'}: in
(::) '"$1"'=$2;; # was empty, copy
(*:"$2":*) ;; # already there, do nothing
(*) '"$1"'=$1:$2;; # otherwise, append with a :
esac'
}
bash
のdeclare
を使用するソリューションと同様に、$1
に有効な変数名が含まれている限り、それはsafeです。
これは非常に簡単に行われ、bash
はまったく必要ありません。これは、基本的なPOSIX指定のパラメータ展開による割り当ての動作です。
: ${PATH:=this is only assigned to \$PATH if \$PATH is null or unset}
@Graemeと同様の方法で、ただしポータブルな方法でデモを行うには:
_fn() { echo "$1 ${2:-"$1"} $str" ; }
% str= ; _fn "${str:=hello}"
> hello hello hello
そして、ここではstr=
のみを実行して、null値が含まれるようにします。これは、パラメータ拡張には、シェル環境をインラインで再割り当てしないための組み込みの保護機能があるためです。すでに設定されている場合。
あなたの特定の問題については、名前付き引数は必要ないと思いますが、それらは確かに可能です。代わりに$IFS
を使用してください:
assign() { oFS=$IFS ; IFS=: ; add=$*
set -- $PATH ; for p in $add ; do {
for d ; do [ -z "${d%"$p"}" ] && break
done ; } || set -- $* $p ; done
PATH= ; echo "${PATH:="$*"}" ; IFS=$oFS
}
実行すると次のようになります。
% PATH=/usr/bin:/usr/yes/bin
% assign \
/usr/bin \
/usr/yes/bin \
/usr/nope/bin \
/usr/bin \
/nope/usr/bin \
/usr/nope/bin
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin
% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin
% dir="/some crazy/dir"
% p=`assign /usr/bin /usr/bin/new "$dir"`
% echo "$p" ; echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new
$PATH
にない引数、または以前に来た引数のみを追加したことに注意してください。それとも、それが複数の引数をとったということですか? $IFS
が便利です。
または...バックスティックを使用
function foo(){
# $1 is first param
# $2 is second param ...
}
呼び出し:
result=`foo "param1" "param2" ...`
# they will go the right way.