Bash'de bir fonksiyon içindeki global bir değişken nasıl değiştirilir?


106

Bununla çalışıyorum:

GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)

Aşağıdaki gibi bir senaryom var:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

test1 
echo "$e"

Hangi döndürür:

hello
4

Ancak fonksiyonun sonucunu bir değişkene atarsam, global değişken edeğiştirilmez:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

ret=$(test1)

echo "$ret"
echo "$e"

İadeler:

hello
2

Bu durumda eval'un kullanıldığını duydum , bu yüzden şunu yaptım test1:

eval 'e=4'

Ama aynı sonuç.

Bana neden değiştirilmediğini açıklar mısınız? test1Fonksiyonun yankısını nasıl kaydedebilirim retve global değişkeni de nasıl değiştirebilirim?


Merhaba dönmen gerekiyor mu? Geri dönmesi için $ e yankılayabilirsiniz. Veya istediğiniz her şeyi yankılayıp sonra sonucu ayrıştırmak mı?

Yanıtlar:


98

Bir komut ikamesi (yani $(...)yapı) kullandığınızda, bir alt kabuk oluşturursunuz. Alt kabuklar, değişkenleri üst kabuklarından devralır, ancak bu yalnızca bir şekilde çalışır - bir alt kabuk, üst kabuğunun ortamını değiştiremez. Değişkeniniz ebir alt kabuk içinde ayarlanır, ancak ana kabukta ayarlanmaz. Değerleri bir alt kabuktan üstüne geçirmenin iki yolu vardır. İlk olarak, stdout'a bir şey çıktı verebilir, sonra onu bir komut ikamesi ile yakalayabilirsiniz:

myfunc() {
    echo "Hello"
}

var="$(myfunc)"

echo "$var"

Verir:

Hello

0-255 arası sayısal bir değer returniçin, numarayı çıkış durumu olarak geçirmek için kullanabilirsiniz :

mysecondfunc() {
    echo "Hello"
    return 4
}

var="$(mysecondfunc)"
num_var=$?

echo "$var - num is $num_var"

Verir:

Hello - num is 4

Konu için teşekkürler, ancak bir dize dizisi döndürmem gerekiyor ve işlevin içinde iki genel dizge dizisine öğeler eklemem gerekiyor.
harrison4

3
Fonksiyonu bir değişkene atamadan çalıştırırsanız, içindeki tüm global değişkenlerin güncelleneceğini fark edersiniz. Bir dizge dizisi döndürmek yerine, neden işlevdeki dize dizisini güncelleyip, işlev bittikten sonra onu başka bir değişkene atamıyorsunuz?

@JohnDoe: Bir işlevden "dizge dizisi" döndüremezsiniz. Tek yapabileceğiniz bir dizi yazdırmaktır. Ancak, şöyle bir şey yapabilirsiniz:setarray() { declare -ag "$1=(a b c)"; }
rici

34

{fd}Veya kullanıyorsanız bu bash 4.1'e ihtiyaç duyar local -n.

Gerisi bash 3.x'te çalışmalıdır umarım. Bundan tam olarak emin değilim printf %q- bu bir bash 4 özelliği olabilir.

Özet

İstenilen efekti arşivlemek için örneğiniz aşağıdaki gibi değiştirilebilir:

# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

e=2

# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
  e=4
  echo "hello"
}

# Change following line to:
capture ret test1 

echo "$ret"
echo "$e"

istendiği gibi yazdırır:

hello
4

Bu çözümün:

  • İçin e=1000de çalışıyor.
  • $?İhtiyacınız olursa korur$?

Tek kötü yan etkiler:

  • Modern bir şeye ihtiyacı var bash.
  • Oldukça daha sık çatallanıyor.
  • Ek açıklamaya ihtiyacı var (eklenmiş olarak işlevinizin adını taşır _)
  • Dosya tanımlayıcısını feda eder 3.
    • İhtiyacınız olursa onu başka bir FD ile değiştirebilirsiniz.
      • Sadece _capturetüm oluşumlarını 3başka bir (daha yüksek) sayıyla değiştirin.

Aşağıdakiler (oldukça uzun, bunun için üzgünüz) umarım bu tarifin diğer komut dosyalarına nasıl uyarlanacağını açıklar.

Sorun

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4

çıktılar

0 20171129-123521 20171129-123521 20171129-123521 20171129-123521

istenen çıktı ise

4 20171129-123521 20171129-123521 20171129-123521 20171129-123521

Sorunun nedeni

Kabuk değişkenleri (veya genel olarak konuşursak, çevre) ebeveyn süreçlerinden alt süreçlere aktarılır, ancak tersi olmaz.

Çıktı yakalama yaparsanız, bu genellikle bir alt kabukta çalıştırılır, bu nedenle değişkenleri geri göndermek zordur.

Hatta bazıları size düzeltmenin imkansız olduğunu söylüyor. Bu yanlıştır, ancak çözülmesi uzun zamandır bilinen bir zordur.

Bunu en iyi şekilde çözmenin birkaç yolu vardır, bu sizin ihtiyaçlarınıza bağlıdır.

İşte nasıl yapılacağına dair adım adım bir kılavuz.

Değişkenleri ebeveyn kabuğuna geri aktarma

Değişkenleri ebeveyn kabuğuna geri göndermenin bir yolu var. Ancak bu tehlikeli bir yoldur, çünkü bu kullanır eval. Yanlış yapılırsa, birçok kötü şeyi riske atarsınız. Ancak doğru şekilde yapılırsa, hata olmaması koşuluyla bu tamamen güvenlidir bash.

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }

x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4

baskılar

4 20171129-124945 20171129-124945 20171129-124945 20171129-124945

Bunun tehlikeli şeyler için de işe yaradığını unutmayın:

danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"

baskılar

; /bin/echo *

Bunun nedeni printf '%q', her şeyi alıntılayan, onu bir kabuk bağlamında güvenle yeniden kullanabilmenizdir.

Ama bu bir acı ...

Bu sadece çirkin görünmekle kalmıyor, aynı zamanda yazması da çok fazla, bu yüzden hataya açık. Tek bir hata ve mahkumsun, değil mi?

Biz kabuk seviyesindeyiz, böylece onu geliştirebilirsiniz. Sadece görmek istediğiniz bir arayüz düşünün ve sonra onu uygulayabilirsiniz.

Artırma, kabuk işleri nasıl işler

Bir adım geri gidelim ve ne yapmak istediğimizi kolayca ifade etmemizi sağlayan bazı API'leri düşünelim.

Peki, bu d()fonksiyonla ne yapmak istiyoruz ?

Çıktıyı bir değişkene yakalamak istiyoruz. Tamam, o zaman tam olarak bunun için bir API uygulayalım:

# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$@")"
}

Şimdi yazmak yerine

d1=$(d)

yazabiliriz

capture d1 d

Pekala, bu pek değişmemiş gibi görünüyor, çünkü yine değişkenler dana kabuğa geri aktarılmıyor ve biraz daha yazmamız gerekiyor.

Ancak şimdi, bir fonksiyona güzelce sarılmış olduğu için, kabuğun tüm gücünü ona atabiliriz.

Yeniden kullanımı kolay bir arayüz düşünün

İkinci bir şey de KURU olmak istiyoruz (Kendinizi Tekrar Etmeyin). Bu yüzden kesinlikle şöyle bir şey yazmak istemiyoruz

x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4

xBurada her zaman doğru bağlamda repeate için 's hata eğilimli, sadece gereksiz olduğunu. Ya onu bir komut dosyasında 1000 kez kullanırsanız ve ardından bir değişken eklerseniz? Bir aramanın ddahil olduğu tüm 1000 konumu kesinlikle değiştirmek istemezsiniz .

Öyleyse xuzak dur, böylece yazabiliriz:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }

xcapture() { local -n output="$1"; eval "$("${@:2}")"; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

çıktılar

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414

Bu zaten çok iyi görünüyor. (Ama hala local -nortak bash3.x'te çalışmayan var )

Değiştirmekten kaçının d()

Son çözümün bazı büyük kusurları var:

  • d() değiştirilmesi gerekiyor
  • xcaptureÇıktıyı geçmek için bazı dahili ayrıntıları kullanması gerekir .
    • Bu değişkeni gölgelendirdiğine (yaktığına) dikkat edin output, böylece bunu asla geri veremeyiz.
  • İşbirliği yapması gerekiyor _passback

Bundan da kurtulabilir miyiz?

Elbette yapabiliriz! Bir kabuğun içindeyiz, bu yüzden bunu yapmak için ihtiyacımız olan her şey var.

Size gelen aramaya biraz daha yakından bakarsanız eval, bu konumda% 100 kontrole sahip olduğumuzu görebilirsiniz. evalBir alt kabuğun " içindeyiz " , böylece ebeveyn kabuğuna kötü bir şey yapma korkusu olmadan istediğimiz her şeyi yapabiliriz.

Evet, güzel, şimdi doğrudan şuraya başka bir sarmalayıcı ekleyelim eval:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${@:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; }  # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

baskılar

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414                                                    

Bununla birlikte, bunun yine bazı büyük dezavantajları vardır:

  • !DO NOT USE!Kolayca göremiyorum bu çok kötü bir yarış durumu, olduğundan belirteçler vardır:
    • Bu >(printf ..)bir arka plan işidir. Yani çalışırken hala _passback xçalışabilir.
    • Bir sleep 1;öncekine printfveya eklerseniz bunu kendiniz görebilirsiniz _passback. _xcapture a d; echosonra sırasıyla çıktılar xveya ailk.
  • ' _passback xNin parçası olmamalıdır _xcapture, çünkü bu, bu tarifin yeniden kullanılmasını zorlaştırır.
  • Ayrıca burada (the $(cat)) bazı unneded fork'larımız var , ancak bu çözüm olduğu !DO NOT USE!için en kısa yolu seçtim.

Ancak bu, değişiklik yapmadan d()(ve değiştirmeden) yapabileceğimizi gösteriyor local -n!

Lütfen _xcaptureherşeyi doğru eval. ' Da yazabileceğimizden, kesinlikle ihtiyacımız olmadığını unutmayın .

Ancak bunu yapmak genellikle çok okunaklı değildir. Ve birkaç yıl içinde senaryonuza geri dönerseniz, muhtemelen çok fazla sorun yaşamadan tekrar okuyabilmek istersiniz.

Yarışı düzelt

Şimdi yarış durumunu düzeltelim.

İşin püf noktası printf, STDOUT'u kapatana kadar beklemek ve ardından çıktı almak olabilir x.

Bunu arşivlemenin birçok yolu vardır:

  • Kabuk boruları kullanamazsınız, çünkü borular farklı işlemlerde çalışır.
  • Geçici dosyalar kullanılabilir,
  • veya bir kilit dosyası veya bir fifo gibi bir şey. Bu kilit veya fifo beklemeye izin verir,
  • veya farklı kanallar, bilginin çıktısını almak ve daha sonra çıktıyı doğru bir sırayla birleştirmek için.

Son yolu izlemek şöyle görünebilir ( printfburada daha iyi çalıştığı için sonuncuyu yaptığına dikkat edin ):

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

_xcapture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; _passback x >&3)"; } 3>&1; }

xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

çıktılar

4 20171129-144845 20171129-144845 20171129-144845 20171129-144845

Bu neden doğru?

  • _passback x doğrudan STDOUT ile konuşuyor.
  • Ancak, STDOUT'un iç komutta yakalanması gerektiğinden, önce onu FD3'e (tabii ki başkalarını da kullanabilirsiniz) '3> & 1' ile "kaydeder" ve sonra tekrar kullanırız >&3.
  • Alt kabuk STDOUT'u $("${@:2}" 3<&-; _passback x >&3)kapattıktan sonra biter _passback.
  • Yani ne kadar sürerse sürsün, printfondan önce olamaz ._passback_passback
  • O Not printfbiz getirilen eserleri göremiyorum böylece komut tam komut önce işlemek yerine, monte edilir printfbağımsız nasıl printfuygulanmaktadır.

Bu nedenle, önce _passbackçalıştırır, sonra printf.

Bu, bir sabit dosya tanımlayıcısını feda ederek yarışı çözer. FD3'ün kabuk komut dosyanızda serbest olmadığı durumda, elbette başka bir dosya tanımlayıcı seçebilirsiniz.

Lütfen 3<&-FD3'ün işleve geçirilmesini koruyan hangisinin olduğunu da not edin .

Daha genel yapın

_captured()yeniden kullanılabilirlik açısından kötü olan ait parçaları içerir . Bunu nasıl çözebilirim?

Pekala, bir şey daha ekleyerek, doğru şeyleri döndürmesi gereken ek bir işlev ekleyerek orijinal işlevin adını taşıyan ek bir işlev ekleyerek tam _anlamıyla yapın.

Bu işlev gerçek işlevden sonra çağrılır ve işleri artırabilir. Bu şekilde, bu bir ek açıklama olarak okunabilir, dolayısıyla çok okunabilirdir:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$@")"; }

d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4

hala baskılar

4 20171129-151954 20171129-151954 20171129-151954 20171129-151954

Dönüş koduna erişime izin ver

Sadece biraz eksik:

v=$(fn)$?neyin fndöndüğünü ayarlar . Yani muhtemelen bunu da istiyorsun. Yine de biraz daha büyük ayarlamalara ihtiyacı var:

# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }

# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf

baskılar

23 42 69 FAIL

Hala iyileştirme için çok yer var

  • _passback() ortadan kaldırılabilir passback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
  • _capture() ile ortadan kaldırılabilir capture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }

  • Çözüm, bir dosya tanımlayıcısını (burada 3) dahili olarak kullanarak kirletmektedir. FD'leri geçerseniz bunu aklınızda bulundurmanız gerekir. 4.1 ve üzeri sürümlerin bazı kullanılmayan FD'leri kullanması
    gerektiğini unutmayın . (Belki buraya geldiğimde buraya bir çözüm ekleyeceğim.) Bu yüzden onu farklı işlevlere koymak için kullandığımı unutmayın .bash{fd}

    _capture , her şeyi tek bir satıra doldurmak mümkün olduğu , ancak okumayı ve anlamayı gittikçe zorlaştırıyor.

  • Belki de çağrılan işlevin STDERR'sini yakalamak istersiniz. Ya da değişkenlerden birden fazla dosya tanımlayıcısını içeri ve dışarı vermek istiyorsunuz.
    Henüz bir çözümüm yok, ancak burada birden fazla FD'yi yakalamanın bir yolu var , bu yüzden muhtemelen değişkenleri bu şekilde de geri verebiliriz.

Ayrıca unutma:

Bu, harici bir komut değil, bir kabuk işlevi çağırmalıdır.

Ortam değişkenlerini harici komutlardan geçirmenin kolay bir yolu yoktur. (Bununla LD_PRELOAD=mümkün olmalı!) Ama o zaman bu tamamen farklı bir şey.

Son sözler

Bu tek olası çözüm değil. Çözüme bir örnektir.

Her zaman olduğu gibi, kabuktaki şeyleri ifade etmenin birçok yolu vardır. Bu yüzden kendinizi geliştirmekten ve daha iyi bir şey bulmaktan çekinmeyin.

Burada sunulan çözüm mükemmel olmaktan oldukça uzaktır:

  • Neredeyse hiç test değildi, bu yüzden lütfen yazım hatalarını affedin.
  • İyileştirme için çok yer var, yukarıya bakın.
  • Modernin birçok özelliğini kullanır bash, bu yüzden muhtemelen diğer mermilere yerleştirmek zordur.
  • Ve düşünmediğim bazı tuhaflıklar olabilir.

Ancak kullanımının oldukça kolay olduğunu düşünüyorum:

  • Yalnızca 4 satır "kitaplık" ekleyin.
  • Kabuk fonksiyonunuz için sadece 1 satır "açıklama" ekleyin.
  • Geçici olarak yalnızca bir dosya tanımlayıcısını feda eder.
  • Ve her adımın yıllar sonra bile anlaşılması kolay olmalıdır.

2
Eğer müthiş
Eliran Malka

14

Belki bir dosya kullanabilir, fonksiyonun içindeki dosyaya yazabilir, sonra dosyadan okuyabilirsiniz. eBir diziye geçtim . Bu örnekte, dizi geri okunurken boşluklar ayırıcı olarak kullanılır.

#!/bin/bash

declare -a e
e[0]="first"
e[1]="secondddd"

function test1 () {
 e[2]="third"
 e[1]="second"
 echo "${e[@]}" > /tmp/tempout
 echo hi
}

ret=$(test1)

echo "$ret"

read -r -a e < /tmp/tempout
echo "${e[@]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"

Çıktı:

hi
first second third
first
second
third

13

Ne yapıyorsun, test1 yapıyorsun

$(test1)

bir alt kabukta (alt kabuk) ve Alt kabuklar üstteki hiçbir şeyi değiştiremez .

Bash el kitabında bulabilirsiniz

Lütfen Kontrol Edin: Burada nesneler bir alt kabukla sonuçlanır


7

Oluşturduğum geçici dosyaları otomatik olarak kaldırmak istediğimde benzer bir sorun yaşadım. Bulduğum çözüm, komut ikamesini kullanmak değil, daha çok nihai sonucu alması gereken değişkenin adını işleve geçirmekti. Örneğin

#! /bin/bash

remove_later=""
new_tmp_file() {
    file=$(mktemp)
    remove_later="$remove_later $file"
    eval $1=$file
}
remove_tmp_files() {
    rm $remove_later
}
trap remove_tmp_files EXIT

new_tmp_file tmpfile1
new_tmp_file tmpfile2

Yani, sizin durumunuzda bu:

#!/bin/bash

e=2

function test1() {
  e=4
  eval $1="hello"
}

test1 ret

echo "$ret"
echo "$e"

Çalışır ve "dönüş değeri" üzerinde herhangi bir kısıtlama yoktur.


1

Bunun nedeni, komut değişiminin bir alt kabukta gerçekleştirilmesidir, bu nedenle alt kabuk değişkenleri devralırken, alt kabuk sona erdiğinde bunlardaki değişiklikler kaybolur.

Referans :

Komut değiştirme , parantezlerle gruplandırılmış komutlar ve eşzamansız komutlar, kabuk ortamının bir kopyası olan bir alt kabuk ortamında çağrılır.


@JohnDoe Bunun mümkün olduğundan emin değilim. Senaryo tasarımınızı yeniden düşünmeniz gerekebilir.
Bir programcı dostum

Oh, ama bir fonksiyon içinde global bir dizi atamalıyım, yoksa, çok fazla kod tekrar etmem gerekir (fonksiyonun kodunu -30 satır- 15 kez-çağrı başına bir- tekrarlayın). Başka yolu yok, değil mi?
harrison4

1

Karmaşık işlevler tanıtmak ve orijinali büyük ölçüde değiştirmek zorunda kalmadan bu soruna bir çözüm, değeri geçici bir dosyada saklamak ve gerektiğinde okumak / yazmaktır.

Bu yaklaşım, bir yarasa test vakasında birden çok kez çağrılan bir bash işleviyle dalga geçmem gerektiğinde bana çok yardımcı oldu.

Örneğin, sahip olabilirsiniz:

# Usage read_value path_to_tmp_file
function read_value {
  cat "${1}"
}

# Usage: set_value path_to_tmp_file the_value
function set_value {
  echo "${2}" > "${1}"
}
#----

# Original code:

function test1() {
  e=4
  set_value "${tmp_file}" "${e}"
  echo "hello"
}


# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)

# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"

# Run test1
test1

# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"

Bunun dezavantajı, farklı değişkenler için birden çok geçici dosyaya ihtiyacınız olabilmesidir. Ayrıca sync, bir yazma ve okuma işlemi arasında diskteki içeriği kalıcı hale getirmek için bir komut vermeniz gerekebilir .


-1

Her zaman bir takma ad kullanabilirsiniz:

alias next='printf "blah_%02d" $count;count=$((count+1))'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.