डुप्लिकेट $ PATH प्रविष्टियों को awk कमांड से निकालें


48

मैं एक बैश शेल फ़ंक्शन लिखने की कोशिश कर रहा हूं जो मुझे अपने पैथ पर्यावरण चर से निर्देशिकाओं की डुप्लिकेट प्रतियां निकालने की अनुमति देगा।

मुझे बताया गया था कि कमांड का उपयोग करके एक लाइन कमांड के साथ इसे प्राप्त करना संभव है awk, लेकिन मैं यह नहीं कर सकता कि यह कैसे करना है। किसी को कैसे पता?



जवाबों:


37

यदि आपके पास पहले से डुप्लिकेट नहीं हैं PATHऔर आप केवल निर्देशिका जोड़ना चाहते हैं यदि वे पहले से ही नहीं हैं, तो आप इसे शेल के साथ आसानी से अकेले कर सकते हैं।

for x in /path/to/add …; do
  case ":$PATH:" in
    *":$x:"*) :;; # already there
    *) PATH="$x:$PATH";;
  esac
done

और यहाँ एक शेल स्निपेट है जो डुप्लिकेट को हटाता है $PATH। यह एक-एक करके प्रविष्टियों के माध्यम से जाता है, और उन प्रतियों को कॉपी करता है जिन्हें अभी तक नहीं देखा गया है।

if [ -n "$PATH" ]; then
  old_PATH=$PATH:; PATH=
  while [ -n "$old_PATH" ]; do
    x=${old_PATH%%:*}       # the first remaining entry
    case $PATH: in
      *:"$x":*) ;;          # already there
      *) PATH=$PATH:$x;;    # not there yet
    esac
    old_PATH=${old_PATH#*:}
  done
  PATH=${PATH#:}
  unset old_PATH x
fi

यह बेहतर होगा, अगर $ पीएटीएच में आइटम्स उलट हो जाएं, क्योंकि बाद में आम तौर पर नए जोड़े जाते हैं, और उनका मूल्य आज तक हो सकता है।
एरिक वांग

2
@ ईरिकवांग मैं आपके तर्क को नहीं समझता। पीएटीएच तत्वों को आगे से पीछे तक लगाया जाता है, इसलिए जब डुप्लिकेट होते हैं, तो दूसरे डुप्लिकेट को प्रभावी रूप से अनदेखा किया जाता है। आगे से पीछे की ओर मुड़ने से क्रम बदल जाता।
गिल्स एसओ- बुराई को रोकें '

@Gilles जब आपने PATH में वैरिएबल डुप्लिकेट किया है, तो संभवत: इसे इस तरह से जोड़ा जाएगा: PATH=$PATH:x=bमूल PATH में x का मान हो सकता है, इस प्रकार जब क्रम में पुनरावृति होती है, तो नए मान को अनदेखा किया जाएगा, लेकिन जब उलटा क्रम में, नया मूल्य प्रभावी होगा।
एरिक वांग

4
@ EricWang उस मामले में, अतिरिक्त मूल्य का कोई प्रभाव नहीं पड़ता है इसलिए इसे अनदेखा किया जाना चाहिए। पीछे की ओर जाने से, आप पहले जोड़े गए मूल्य को बना रहे हैं। यदि जोड़ा गया मूल्य पहले जाना चाहिए था, तो इसे जोड़ा जाएगा PATH=x:$PATH
गिल्स एसओ- बुराई को रोकना '

@ गिल्स जब आप किसी चीज़ को जोड़ते हैं, तो इसका मतलब है कि यह अभी तक नहीं है, या आप पुराने मूल्य को ओवरराइड करना चाहते हैं, इसलिए आपको नए जोड़े गए चर को दिखाई देने की आवश्यकता है। और, सम्मेलन द्वारा, आमतौर पर यह इस तरह से संलग्न होता है: PATH=$PATH:...नहीं PATH=...:$PATH। इस प्रकार यह उलट क्रम को पुनरावृत्त करने के लिए अधिक उचित है। हालांकि आप जिस तरह से भी काम करेंगे, तब लोग उल्टे तरीके से अपेंडमेंट करेंगे।
एरिक वांग

23

यहां एक समझदार वन-लाइनर समाधान है जो सभी सही चीजें करता है: डुप्लिकेट को हटाता है, रास्तों के आदेश को संरक्षित करता है, और अंत में एक कोलोन नहीं जोड़ता है। तो यह आपको एक कटौती योग्य PATH देना चाहिए जो मूल के समान व्यवहार देता है:

PATH="$(perl -e 'print join(":", grep { not $seen{$_}++ } split(/:/, $ENV{PATH}))')"

यह बस बृहदान्त्र पर विभाजन करता है ( split(/:/, $ENV{PATH})), grep { not $seen{$_}++ }पहली घटना को छोड़कर पथ के किसी भी दोहराया उदाहरणों को फ़िल्टर करने के लिए उपयोग का उपयोग करता है, और फिर शेष लोगों को कॉलोनियों द्वारा अलग किए गए वापस जोड़ता है और परिणाम ( print join(":", ...)) प्रिंट करता है ।

यदि आप इसके चारों ओर कुछ और संरचना चाहते हैं, साथ ही साथ अन्य चर को भी कम करने की क्षमता रखते हैं, तो इस स्निपेट को आज़माएं, जिसका उपयोग मैं वर्तमान में अपने स्वयं के विन्यास में कर रहा हूँ:

# Deduplicate path variables
get_var () {
    eval 'printf "%s\n" "${'"$1"'}"'
}
set_var () {
    eval "$1=\"\$2\""
}
dedup_pathvar () {
    pathvar_name="$1"
    pathvar_value="$(get_var "$pathvar_name")"
    deduped_path="$(perl -e 'print join(":",grep { not $seen{$_}++ } split(/:/, $ARGV[0]))' "$pathvar_value")"
    set_var "$pathvar_name" "$deduped_path"
}
dedup_pathvar PATH
dedup_pathvar MANPATH

यह कोड PATH और MANPATH दोनों को काट देगा, और आप dedup_pathvarअन्य चर पर आसानी से कॉल कर सकते हैं जो कि पथों की बृहदान्त्र-पृथक सूचियों (जैसे PYTHONPATH) को धारण करते हैं।


किसी कारण से मुझे chompएक अनुगामी न्यूलाइन निकालने के लिए जोड़ना पड़ा । इसने मेरे लिए काम किया:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Håkon H forgland

12

यहाँ एक चिकना है:

printf %s "$PATH" | awk -v RS=: -v ORS=: '!arr[$0]++'

लंबा (यह देखने के लिए कि यह कैसे काम करता है):

printf %s "$PATH" | awk -v RS=: -v ORS=: '{ if (!arr[$0]++) { print $0 } }'

ठीक है, चूंकि आप linux में नए हैं, यहाँ बताया गया है कि वास्तव में एक अनुगामी के बिना PATH को कैसे सेट करें: "

PATH=`printf %s "$PATH" | awk -v RS=: '{ if (!arr[$0]++) {printf("%s%s",!ln++?"":":",$0)}}'`

btw सुनिश्चित करें कि निर्देशिका को आपके PATH में ":" नहीं होना चाहिए, अन्यथा यह गड़बड़ होने वाला है।

कुछ श्रेय:


-1 यह काम नहीं करता है। मैं अभी भी अपने रास्ते में डुप्लिकेट देखता हूं।
डोगबेन

4
@ डोगबेन: यह मेरे लिए डुप्लिकेट को हटा देता है। हालाँकि इसमें एक सूक्ष्म समस्या है। आउटपुट में एक है: अंत में जो अगर आपके $ PATH के रूप में सेट किया गया है, तो इसका मतलब है कि वर्तमान निर्देशिका को पथ जोड़ा गया है। यह एक बहु-उपयोगकर्ता मशीन पर सुरक्षा निहितार्थ है।
कैम

@dogbane, यह काम करता है और मैंने पोस्ट को
एडिटिंग के

@dogbane आपके समाधान में एक अनुगामी है: आउटपुट में
akostadinov

हम्म, आपका तीसरा कमांड काम करता है, लेकिन पहले दो तब तक काम नहीं करते जब तक मैं उपयोग नहीं करता echo -n। आपके आदेश "यहाँ तार" के साथ काम करने के लिए प्रतीत नहीं होता है जैसे कि कोशिश करें:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
dogbane

6

यहाँ एक AWK एक लाइनर है।

$ PATH=$(printf %s "$PATH" \
     | awk -vRS=: -vORS= '!a[$0]++ {if (NR>1) printf(":"); printf("%s", $0) }' )

कहाँ पे:

  • printf %s "$PATH"$PATHएक अनुगामी न्यूलाइन के बिना सामग्री प्रिंट करता है
  • RS=: इनपुट रिकॉर्ड सीमांकक वर्ण परिवर्तित करता है (डिफ़ॉल्ट नई रेखा है)
  • ORS= रिक्त स्ट्रिंग में आउटपुट रिकॉर्ड सीमांकक को परिवर्तित करता है
  • a एक अंतर्निहित सरणी का नाम
  • $0 वर्तमान रिकॉर्ड का संदर्भ देता है
  • a[$0] एक साहचर्य सरणी dereference है
  • ++ वेतन वृद्धि ऑपरेटर है
  • !a[$0]++ दाहिने हाथ की ओर गार्ड, यानी यह सुनिश्चित करता है कि वर्तमान रिकॉर्ड केवल मुद्रित है, अगर यह पहले मुद्रित नहीं किया गया था
  • NR वर्तमान रिकॉर्ड संख्या, 1 से शुरू

इसका मतलब है कि AWK का उपयोग सीमांकक वर्णों के PATHसाथ सामग्री को विभाजित :करने और आदेश को संशोधित किए बिना डुप्लिकेट प्रविष्टियों को फ़िल्टर करने के लिए किया जाता है।

चूंकि AWK साहचर्य सरणियों को हैश टेबल के रूप में कार्यान्वित किया जाता है, रनटाइम रैखिक होता है (यानी O (n) में)।

ध्यान दें कि हमें उद्धृत :वर्णों की तलाश करने की आवश्यकता नहीं है क्योंकि गोले चर में इसके नाम के साथ निर्देशिकाओं का समर्थन नहीं करते हैं:PATH

अक्क + पेस्ट

उपरोक्त पेस्ट के साथ सरलीकृत किया जा सकता है:

$ PATH=$(printf %s "$PATH" | awk -vRS=: '!a[$0]++' | paste -s -d:)

pasteआदेश कोलन साथ awk उत्पादन गिरा करने के लिए प्रयोग किया जाता है। यह मुद्रण के लिए awk क्रिया को सरल करता है (जो कि डिफ़ॉल्ट क्रिया है)।

अजगर

पायथन दो-लाइनर के रूप में ही:

$ PATH=$(python3 -c 'import os; from collections import OrderedDict; \
    l=os.environ["PATH"].split(":"); print(":".join(OrderedDict.fromkeys(l)))' )

ठीक है, लेकिन क्या यह एक मौजूदा बृहदान्त्र सीमांकित स्ट्रिंग से ड्यूप्स को हटाता है, या क्या यह किसी स्ट्रिंग में जोड़े जाने से रोकता है?
अलेक्जेंडर मिल्स

1
पूर्व की तरह दिखता है
अलेक्जेंडर मिल्स

2
@AlexanderMills, ठीक है, ओपी ने सिर्फ डुप्लिकेट को हटाने के बारे में पूछा ताकि यह आवक कॉल करता है।
मैक्सक्लेपज़िग

1
pasteआदेश मेरे लिए काम नहीं करता जब तक कि मैं पीछे जोड़ने -STDIN उपयोग करने के लिए।
वारबैंक

2
इसके अलावा, मुझे -vएक त्रुटि मिलने के बाद रिक्त स्थान जोड़ने की आवश्यकता है । -v RS=: -v ORS=awkसिंटैक्स के बस अलग-अलग स्वाद ।
वारबैंक

4

इस बारे में यहां इसी तरह की चर्चा हुई है

मैं थोड़ा अलग तरीका अपनाता हूं। केवल उन सभी अलग-अलग इनिशियलाइज़ेशन फ़ाइलों से जो PATH स्थापित है, को स्वीकार करने के बजाय, मैं getconfसिस्टम पथ को पहचानने और पहले स्थान पर उपयोग करना पसंद करता हूं , फिर अपना पसंदीदा पथ क्रम जोड़ें, फिर awkकिसी भी डुप्लिकेट को निकालने के लिए उपयोग करें। यह वास्तव में कमांड निष्पादन को गति दे सकता है या नहीं कर सकता है (और सिद्धांत रूप में अधिक सुरक्षित हो सकता है), लेकिन यह मुझे गर्म फजीज देता है।

# I am entering my preferred PATH order here because it gets set,
# appended, reset, appended again and ends up in such a jumbled order.
# The duplicates get removed, preserving my preferred order.
#
PATH=$(command -p getconf PATH):/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# Remove duplicates
PATH="$(printf "%s" "${PATH}" | /usr/bin/awk -v RS=: -v ORS=: '!($0 in a) {a[$0]; print}')"
export PATH

[~]$ echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/lib64/ccache:/usr/games:/home/me/bin

3
यह बहुत ही खतरनाक है क्योंकि आप पीछे जोड़ने :के लिए PATH, (यानी कोई रिक्त स्ट्रिंग प्रविष्टि) क्योंकि तब वर्तमान कार्यशील निर्देशिका अपने का हिस्सा है PATH
मैक्सचेलपिजिग

3

जब तक हम नॉन-वॉक ऑनलाइनर जोड़ रहे हैं:

PATH=$(zsh -fc "typeset -TU P=$PATH p; echo \$P")

(जितना आसान हो सकता है, PATH=$(zsh -fc 'typeset -U path; echo $PATH')लेकिन zsh हमेशा कम से कम एक zshenvकॉन्फ़िगरेशन फ़ाइल पढ़ता है , जो संशोधित कर सकता है PATH।)

यह दो अच्छे zsh फीचर्स का उपयोग करता है:

  • सरणियों से बंधे स्केलर ( typeset -T)
  • और सरणियाँ डुप्लिकेट मानों को ऑटोरेमोव ( typeset -U)।

अच्छा! अंत में बृहदान्त्र के बिना सबसे कम काम कर रहे उत्तर, और मूल रूप से।
जाप

2
PATH=`perl -e 'print join ":", grep {!$h{$_}++} split ":", $ENV{PATH}'`
export PATH

यह पर्ल का उपयोग करता है और इसके कई लाभ हैं:

  1. यह डुप्लिकेट को हटाता है
  2. यह क्रमबद्ध रहता है
  3. यह जल्द से जल्द उपस्थिति रखता है ( /usr/bin:/sbin:/usr/binपरिणाम में होगा /usr/bin:/sbin)

2

इसके अलावा sed(यहां GNU sedसिंटैक्स का उपयोग करके ) काम कर सकते हैं:

MYPATH=$(printf '%s\n' "$MYPATH" | sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb')

यह केवल अच्छी तरह से काम करता है केवल मामले में पहला रास्ता .डॉगबैन के उदाहरण में है।

सामान्य स्थिति में आपको एक और sकमांड जोड़ना होगा :

MYPATH=$(printf '%s\n' "$MYPATH" | sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb;s/^\([^:]*\)\(:.*\):\1/:\1\2/')

यह इस तरह के निर्माण पर भी काम करता है:

$ echo "/bin:.:/foo/bar/bin:/usr/bin:/foo/bar/bin:/foo/bar/bin:/bar/bin:/usr/bin:/bin" \
| sed ':b;s/:\([^:]*\)\(:.*\):\1/:\1\2/;tb;s/^\([^:]*\)\(:.*\):\1/\1\2/'

/bin:.:/foo/bar/bin:/usr/bin:/bar/bin

2

जैसा कि दूसरों ने प्रदर्शित किया है कि यह एक लाइन में संभव है जो कि awk, sed, perl, zsh या bash का उपयोग कर रहा है, लंबी लाइनों और पठनीयता के लिए आपकी सहनशीलता पर निर्भर करता है। यहाँ एक बश फ़ंक्शन है कि

  • डुप्लिकेट निकालता है
  • आदेश सुरक्षित रखता है
  • निर्देशिका नामों में रिक्त स्थान की अनुमति देता है
  • आपको सीमांकक निर्दिष्ट करने की अनुमति देता है (':')
  • PATH ही नहीं, अन्य चरों के साथ भी इसका उपयोग किया जा सकता है
  • अगर आप OS X का उपयोग करते हैं, तो bash संस्करण <4 में काम करना महत्वपूर्ण है, जो bash संस्करण 4 को शिप नहीं करता है

समारोह को नष्ट करें

remove_dups() {
    local D=${2:-:} path= dir=
    while IFS= read -d$D dir; do
        [[ $path$D =~ .*$D$dir$D.* ]] || path+="$D$dir"
    done <<< "$1$D"
    printf %s "${path#$D}"
}

प्रयोग

PATH से डुबकी हटाने के लिए

PATH=$(remove_dups "$PATH")

1

यह मेरा संस्करण है:

path_no_dup () 
{ 
    local IFS=: p=();

    while read -r; do
        p+=("$REPLY");
    done < <(sort -u <(read -ra arr <<< "$1" && printf '%s\n' "${arr[@]}"));

    # Do whatever you like with "${p[*]}"
    echo "${p[*]}"
}

उपयोग: path_no_dup "$PATH"

नमूना उत्पादन:

rany$ v='a:a:a:b:b:b:c:c:c:a:a:a:b:c:a'; path_no_dup "$v"
a:b:c
rany$

1

हाल के बैश संस्करण (> = 4) भी साहचर्य सरणियों के हैं, यानी आप इसके लिए एक बैश 'वन लाइनर' का भी उपयोग कर सकते हैं:

PATH=$(IFS=:; set -f; declare -A a; NR=0; for i in $PATH; do NR=$((NR+1)); \
       if [ \! ${a[$i]+_} ]; then if [ $NR -gt 1 ]; then echo -n ':'; fi; \
                                  echo -n $i; a[$i]=1; fi; done)

कहाँ पे:

  • IFS के लिए इनपुट फ़ील्ड विभाजक को बदलता है :
  • declare -A एक सहयोगी सरणी घोषित करता है
  • ${a[$i]+_}एक पैरामीटर विस्तार अर्थ है: _प्रतिस्थापित किया जाता है अगर और केवल अगर a[$i]सेट किया गया है। यह भी ऐसा ही है, ${parameter:+word}जो नॉट-नल के लिए भी टेस्ट करता है। इस प्रकार, सशर्त के निम्नलिखित मूल्यांकन में, अभिव्यक्ति _(यानी एक एकल वर्ण स्ट्रिंग) सही का मूल्यांकन करता है (यह इसके बराबर है -n _) - जबकि एक खाली अभिव्यक्ति झूठी का मूल्यांकन करती है।

+1: अच्छी स्क्रिप्ट शैली, लेकिन क्या आप विशेष वाक्यविन्यास की व्याख्या कर सकते हैं: ${a[$i]+_}अपना उत्तर संपादित करके और एक बुलेट जोड़कर। बाकी पूरी तरह से समझ में आता है लेकिन आपने मुझे वहां खो दिया। धन्यवाद।
Cbhihe

1
@ कही, मैंने एक बुलेट पॉइंट जोड़ा है जो इस विस्तार को संबोधित करता है।
मैक्सक्लेपज़िग

आपका बहुत बहुत धन्यवाद। बहुत ही रोचक। मैंने नहीं सोचा था कि सरणियों (गैर-तार) के साथ संभव था ...
Cbhihe

1
PATH=`awk -F: '{for (i=1;i<=NF;i++) { if ( !x[$i]++ ) printf("%s:",$i); }}' <<< "$PATH"`

Awk कोड की व्याख्या:

  1. कॉलोन्स द्वारा इनपुट को अलग करें।
  2. तेजी से डुप्लिकेट लुक-अप के लिए साहचर्य सरणी में नई पथ प्रविष्टियां जोड़ें।
  3. सहयोगी सरणी प्रिंट।

चर्मकार होने के अलावा, यह वन-लाइनर तेज है: awk amortized O (1) प्रदर्शन को प्राप्त करने के लिए एक चेनिंग हैश-टेबल का उपयोग करता है।

डुप्लिकेट $ PATH प्रविष्टियों को हटाने पर आधारित है


पुरानी पोस्ट, लेकिन क्या आप समझा सकते हैं if ( !x[$i]++ ):। धन्यवाद।
Cbhihe

0

awkपथ को विभाजित करने के लिए उपयोग करें :, फिर प्रत्येक फ़ील्ड पर लूप करें और इसे एक सरणी में संग्रहीत करें। यदि आप एक ऐसे क्षेत्र में आते हैं जो पहले से ही सरणी में है, तो इसका मतलब है कि आपने इसे पहले देखा है, इसलिए इसे प्रिंट न करें।

यहाँ एक उदाहरण है:

$ MYPATH=.:/foo/bar/bin:/usr/bin:/foo/bar/bin
$ awk -F: '{for(i=1;i<=NF;i++) if(!($i in arr)){arr[$i];printf s$i;s=":"}}' <<< "$MYPATH"
.:/foo/bar/bin:/usr/bin

(अनुगामी को हटाने के लिए अद्यतन किया गया :।)


0

एक समाधान - एक ऐसा नहीं है जो उतना ही सुरुचिपूर्ण है जो * आरएस चर को बदलते हैं, लेकिन शायद बहुत स्पष्ट हैं:

PATH=`awk 'BEGIN {np="";split(ENVIRON["PATH"],p,":"); for(x=0;x<length(p);x++) {  pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np ":"; np=np pe}} END { print np }' /dev/null`

पूरा कार्यक्रम BEGIN और END ब्लॉक में काम करता है । यह आपके PATH वैरिएबल को पर्यावरण से खींचकर, यूनिटों में विभाजित करता है। यह तब परिणामी सरणी p (जो क्रम में बनाई गई है split()) से अधिक पुनरावृत्त करता है । सरणी एक साहचर्य सरणी है जिसका उपयोग यह निर्धारित करने के लिए किया जाता है कि हमने वर्तमान पथ तत्व (जैसे / usr / स्थानीय / बिन ) को देखा है या नहीं , और यदि नहीं, तो np को जोड़ा जाता है , तर्क के साथ बृहदान्त्र को जोड़ने के लिए। एनपी अगर वहाँ पहले से ही में पाठ है एनपीअंत ब्लॉक बस echos एनपी । इसे आगे जोड़कर सरल बनाया जा सकता है-F:ध्वज, तीसरे तर्क को समाप्त करना split()(जैसा कि यह FS को चूकता है ), और हमें बदलते np = np ":"हुए np = np FS, हमें दे रहा है:

awk -F: 'BEGIN {np="";split(ENVIRON["PATH"],p); for(x=0;x<length(p);x++) {  pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np FS; np=np pe}} END { print np }' /dev/null

वास्तव में, मेरा मानना ​​था कि for(element in array)आदेश को संरक्षित करेगा, लेकिन ऐसा नहीं है, इसलिए मेरा मूल समाधान काम नहीं करता है, क्योंकि लोग अचानक परेशान हो जाएंगे अगर किसी ने अचानक उनके आदेश को तोड़ दिया $PATH:

awk 'BEGIN {np="";split(ENVIRON["PATH"],p,":"); for(x in p) { pe=p[x]; if(e[pe] != "") continue; e[pe] = pe; if(np != "") np=np ":"; np=np pe}} END { print np }' /dev/null

0
export PATH=$(echo -n "$PATH" | awk -v RS=':' '(!a[$0]++){if(b++)printf(RS);printf($0)}')

केवल पहली घटना रखी जाती है और सापेक्ष क्रम अच्छी तरह से बनाए रखा जाता है।


-1

मैं इसे मूल उपकरण जैसे tr, सॉर्ट और यूनीक के साथ करूंगा:

NEW_PATH=`echo $PATH | tr ':' '\n' | sort | uniq | tr '\n' ':'`

अगर आपके रास्ते में कुछ खास या अजीब नहीं है तो यह काम करना चाहिए


btw, आप के sort -uबजाय का उपयोग कर सकते हैं sort | uniq
भीड़

11
चूंकि पाथ तत्वों का क्रम महत्वपूर्ण है, इसलिए यह बहुत उपयोगी नहीं है।
मैक्सक्लेपजिग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.