একবারে অনুরূপ বাশ ফাংশন কীভাবে সংজ্ঞায়িত করা যায়


10

আমার এই ফাংশনগুলিতে রয়েছে ~/.bashrc:

function guard() {
    if [ -e 'Gemfile' ]; then
    bundle exec guard "$@"
    else
    command guard "$@"
    fi
}
function rspec() {
    if [ -e 'Gemfile' ]; then
    bundle exec rspec "$@"
    else
    command rspec "$@"
    fi
}
function rake() {
    if [ -e 'Gemfile' ]; then
        bundle exec rake "$@"
    else
        command rake "$@"
    fi
}

আপনি এই ফাংশন দেখতে খুব অনুরূপ। আমি এই 3 টি কার্য একবারে সংজ্ঞায়িত করতে চাই। এটি তৈরি করার কোনও উপায় আছে?

পরিবেশ

bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)

উত্তর:


8
$ cat t.sh
#!/bin/bash

for func in guard rspec rake; do
        eval "
        ${func}() {
                local foo=(command ${func})
                [ -e 'Gemfile' ] && foo=(bundle exec ${func})
                \"\${foo[@]}\" \"\$@\"
        }
        "
done

type guard rspec rake

$ ./t.sh
guard is a function
guard ()
{
    local foo=(command guard);
    [ -e 'Gemfile' ] && foo=(bundle exec guard);
    "${foo[@]}" "$@"
}
rspec is a function
rspec ()
{
    local foo=(command rspec);
    [ -e 'Gemfile' ] && foo=(bundle exec rspec);
    "${foo[@]}" "$@"
}
rake is a function
rake ()
{
    local foo=(command rake);
    [ -e 'Gemfile' ] && foo=(bundle exec rake);
    "${foo[@]}" "$@"
}

evalআবেদন সম্পর্কে স্বাভাবিক সতর্কতা ।


এটি কি for loop?আমার অর্থ দ্বারা খাওয়া হচ্ছে না , for loopসাধারণত ঘোষিত ভেরিয়েবলগুলি অদৃশ্য হয়ে যায় - আমি একই কারণে একই কার্যকারিতা আশা করব।
মাইকজার্ভ

এটা চিন্তা করছো কেন? bash -c 'for i in 1; do :; done; echo $i'=> 1typeপরিষ্কারভাবে দেখায় ফাংশন লুপ আওতার বাইরে অস্তিত্ব যে।
অ্যাড্রিয়ান ফ্রাওয়ার্থিথ

1
@ মিমিকারভোস এমনকি bashডায়নামিক স্কোপিংয়ের মাধ্যমে আপনি যেগুলি পেতে পারেন তা পুরো ফাংশনেরlocal পরিধি হিসাবে একটি ভেরিয়েবল স্থানীয় , ভেরিয়েবলগুলি অবশ্যই একটি লুপের পরে "অদৃশ্য" হয় না। আসলে, যেহেতু এখানে কোনও ক্রিয়াকলাপ জড়িত নেই তাই এই ক্ষেত্রে কোনও ভেরিয়েবল সংজ্ঞা দেওয়া সম্ভবও নয় । local
অ্যাড্রিয়ান ফ্রাওয়ার্থিথ

ডান - লুপের জন্য স্থানীয় - সেগুলি স্থানীয়ভাবে স্কোপযুক্ত। পিতামাতার লুপ শেলটি হওয়ার সাথে সাথে এগুলি অদৃশ্য হয়ে যায়। এখানে কি ঘটে না?
মাইক্রজারভ

না, যেমনটি আমি কেবল ব্যাখ্যা করেছি শেল স্ক্রিপ্টিংয়ে "স্থানীয় থেকে লুপের জন্য" এর মতো কোনও ধারণা নেই এবং আমার পোস্ট এবং উপরে আমার মন্তব্যে উদাহরণটি পরিষ্কারভাবে এটি দেখায়।
অ্যাড্রিয়ান ফ্রেহওয়ার্থ

7
_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."

উপরের উইল . source /dev/fd/3যা _gem_dec()ফাংশনে প্রতিবার খাওয়ানো হয় here-document. _gem_dec'sকেবল প্রাক-মূল্যায়নকৃত কাজ হিসাবে এটি একটি প্যারামিটার গ্রহণ করা হয় এবং এটি bundle execলক্ষ্য হিসাবে এবং এটি যে ফাংশনটিতে লক্ষ্যবস্তু হয়েছিল তার নাম হিসাবে উভয়ই পূর্ব-মূল্যায়ন করে ।

NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.

উপরের ক্ষেত্রে যদিও, আমি মনে করি না যে কোনও ঝুঁকি থাকতে পারে।

উপরের কোড-ব্লক একটি অনুলিপি করা হয়, তাহলে .bashrcফাইল না শুধুমাত্র শেল ফাংশন হবে _guard(), _rspec()এবং_rake() লগইন এ ঘোষণা, কিন্তু _gem_dec()ফাংশন এছাড়াও আপনার শেল যে কোনও সময়ে সঞ্চালনের জন্য উপলব্ধ করা হবে প্রম্পট (বা অন্যভাবে) এবং তাই নতুন টেমপ্লেট করা ফাংশন করতে পারেন আপনি যখনই ন্যায়সঙ্গতভাবে পছন্দ করবেন ঘোষণা করুন:

_gem_dec $new_templated_function_name

এবং আমাকে দেখানোর জন্য @ অ্যান্ড্রুকে ধন্যবাদ যে এগুলি খাওয়া হবে না for loop.

কিন্তু কিভাবে?

আমি 3উপরের ফাইল বর্ণনাকারীটিকে stdin, stdout, and stderr, or <&0 >&1 >&2অভ্যাসের বাইরে রাখার জন্য ব্যবহার করি - যদিও, আমি এখানে প্রয়োগ করা অন্যান্য পূর্বনির্ধারিত কয়েকটি সাবধানতার জন্য যেমন হয় - কারণ ফলস্বরূপ ফাংশনটি এত সহজ, এটি সত্যই প্রয়োজনীয় নয়। এটি ভাল অনুশীলন, যদিও। কল shift $#করা সেই অপ্রয়োজনীয় সাবধানতাগুলির মধ্যে একটি।

এখনও, যখন একটি ফাইল হিসাবে নির্দিষ্ট করা <inputবা>output সঙ্গে [optional num]<fileবা [optional num]>fileফেরৎ কার্নেল একটি ফাইল বর্ণনাকারী মাধ্যমে অ্যাক্সেস করা যেতে পারে যা এটি সার্চ character deviceবিশেষ ফাইল /dev/fd/[0-9]*যদি [optional num]নির্দিষ্টকারীটি বাদ দেওয়া হয় তবে তারপরে 0<fileইনপুট এবং 1>fileআউটপুট গ্রহণ করা হয়। এই বিবেচনা:

l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6

( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2

( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6

( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3

( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6

এবং কারণ here-documentযখন আমরা একটি কোড-ব্লকের মধ্যে কোনও ফাইলের ইনলাইন বর্ণনা করার কেবলমাত্র একটি মাধ্যম:

<<'HEREDOC'
[$CODE]
HEREDOC

আমরা পাশাপাশি করতে পারে:

echo '[$CODE]' >/dev/fd/0

একটি খুব গুরুত্বপূর্ণ পার্থক্য সঙ্গে। আপনি যদি এটির একটি না "'\quote'"করেন তবে শেলটি শেলের মতো এটির জন্য মূল্যায়ন করবে :<<"'\LIMITER"'here-document$expansion

echo "[$CODE]" >/dev/fd/0

সুতরাং, জন্য _gem_dec(),3<<-FUNC here-document ইনপুট একটি ফাইল, একই যেমন যদি এটা ছিল হবে যেমন মূল্যায়ন করা হয় 3<~/some.file ব্যতীত যে কারণ আমরা চলে FUNCসীমা কোট মুক্ত, এটা প্রথম মূল্যায়ন করা হয় $expansion.এই সম্পর্কে গুরুত্বপূর্ণ বিষয় এটি ইনপুট, অর্থ হয় এটি কেবলমাত্র এর জন্য বিদ্যমান _gem_dec(),তবে _gem_dec()ফাংশনটি চালানোর $expansionsআগে এটির মূল্যায়নও করা হয় কারণ আমাদের শেলটি ইনপুট হিসাবে বন্ধ করার আগে এটি পড়তে এবং মূল্যায়ন করতে হয়।

guard,উদাহরণস্বরূপ করা যাক :

_gem_dec guard

সুতরাং প্রথমে শেলটি ইনপুটটি পরিচালনা করতে হবে যার অর্থ পঠন:

3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC

ফাইল বর্ণনাকারী 3 এ প্রবেশ করুন এবং শেল বিস্তারের জন্য এটি মূল্যায়ন করছে। যদি আপনি এই সময় দৌড়েছিলেন:

cat /dev/fd/3

বা:

cat <&3

যেহেতু তারা উভয়ই সমান কমান্ড হ'ল আপনি দেখতে পাচ্ছেন *:

_guard() { [ ! -e 'Gemfile' ] && { 
    command guard "$@" ; return $?
    } || bundle exec guard "$@"
}

... ফাংশনের কোনও কোড আগে কার্যকর করা যায়। এটি সব পরে <input, ফাংশন এর । আরও উদাহরণের জন্য আমার এখানে একটি পৃথক প্রশ্নের উত্তর দেখুন ।

(* প্রযুক্তিগতভাবে এটি পুরোপুরি সত্য নয়। কারণ আমি প্রথমটির -dashআগে একটি শীর্ষস্থানীয় ব্যবহার করি here-doc limiter, উপরের সমস্তটি বাম-ন্যায়সঙ্গত হবে But তবে আমি প্রথমটি পাঠযোগ্যতার জন্য -dashএতটা ব্যবহার করেছি যাতে আমি <tab-insert>এর <tab-inserts>আগে ফেলা যাব না not এটি পড়ার জন্য আপনাকে অফার ...)

এ সম্পর্কে সর্বোত্তম অংশটি উদ্ধৃতি - লক্ষ্য করুন যে '"উদ্ধৃতিগুলি রয়ে গেছে এবং কেবল \উদ্ধৃতিগুলি কেড়ে নেওয়া হয়েছিল। এটি সম্ভবত অন্য যে কোনও কারণের চেয়ে বেশি কারণ আপনি যদি একটি শেলকে দুবার মূল্যায়ন করতে হয় তবে $expansionআমি সুপারিশ করব here-documentকারণ উদ্ধৃতিগুলির চেয়ে অনেক সহজ eval

যাইহোক, এখন উপরের কোডটি ঠিক তেমন একটি ফাইল খাওয়ানো ঠিক যেমন ফাংশনটির 3<~/heredoc.fileজন্য অপেক্ষা করার অপেক্ষা রাখে _gem_dec()এবং তার ইনপুটটি গ্রহণ করে /dev/fd/3

সুতরাং আমরা যখন _gem_dec()প্রথম কাজটি শুরু করি তখন সমস্ত অবস্থানগত পরামিতি টস করা হয়, কারণ আমাদের পরবর্তী পদক্ষেপটি দ্বিগুণ মূল্যায়নকৃত শেল প্রসার এবং আমি চাই না যে থাকা কোনওটিই $expansionsআমার বর্তমান $1 $2 $3...পরামিতিগুলির মতো ব্যাখ্যা করা হোক । তাই আমি:

shift $#

shiftpositional parametersআপনি নির্দিষ্ট হিসাবে যতগুলি বাতিল এবং $1যা থেকে শুরু হয় তা থেকে শুরু করে । সুতরাং যদি আমি নামক _gem_dec one two threeপ্রম্পটে _gem_dec's $1 $2 $3অবস্থানগত পরামিতি হবে one two threeএবং মোট বর্তমান অবস্থানগত গণনা, বা $#3. হবে আমি তখন বলা যদি shift 2,মান oneএবংtwo হবে shifted মান দূরে, $1পরিবর্তন করবে threeএবং $#প্রসারিত হবে 1. সুতরাং shift $#শুধু তাদের সব ফেলে দেয়। এটি করা কঠোরভাবে সতর্কতামূলক এবং কিছুক্ষণের জন্য এই ধরণের কাজ করার পরে আমি বিকাশ করেছি habit (subshell)স্পষ্টতার স্বার্থে এটি এখানে কিছুটা ছড়িয়ে পড়েছে:

( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3

( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1

( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0

যাইহোক, পরবর্তী পদক্ষেপটি যেখানে যাদুটি ঘটে। আপনি যদি . ~/some.shশেল প্রম্পটে থাকেন তবে ঘোষণা করা সমস্ত ফাংশন এবং পরিবেশের ভেরিয়েবলগুলি ~/some.shআপনার শেল প্রম্পটে কলযোগ্য হবে। একই সত্য এখানে হল, আমরা ছাড়া আমাদের ফাইলের বর্ণনাকারী জন্য বিশেষ ফাইল, বা - যা যেখানে আমাদের ইন-লাইন ফাইল pathed করা হয়েছে - এবং আমরা আমাদের ফাংশন ঘোষণা করেছি। এবং এটি কিভাবে এটি কাজ করে।. sourcecharacter device. /dev/fd/3here-document

_guard

আপনার _guardফাংশনটি করার কথা এখনই তা করে ।

সংযোজন:

আপনার অবস্থানগুলি সংরক্ষণ করুন বলার দুর্দান্ত উপায়:

f() { . /dev/fd/3
} 3<<-ARGS
    args='${args:-"$@"}'
ARGS

সম্পাদনা করুন:

আমি যখন প্রথম এই প্রশ্নের উত্তর দিয়েছি তখন আমি function()অন্যান্য $ENVশল্য ক্রিয়াকলাপের বিষয়ে সক্ষম শেল ঘোষণার সমস্যার দিকে আরও বেশি মনোযোগ দিয়েছিলাম যা বর্তমান শেল আইরনমেন্টে অবিচল থাকবে এবং জিজ্ঞাসাবাদীর অবিচ্ছিন্ন ক্রিয়াকলাপগুলির সাথে আমি কী করব তার চেয়ে বেশি করেছিলাম। সেই থেকে আমি বুঝতে পেরেছি যে আমার মূলত লাভযুক্ত সমাধান যা 3<<-FUNCফর্মটি নিয়েছিল:

3<<-FUNC
    _${1}() { 
        if [ -e 'Gemfile' ]; then
            bundle exec $1 "\$@"
        else 
            command _${1} "\$@"
    }
FUNC

সম্ভাবনা প্রশ্নকর্তা জন্য প্রত্যাশিত কারণ আমি বিশেষভাবে থেকে ঘোষণামূলক ফাংশনের নাম পরিবর্তন কাজ হতো না $1করতে _${1}, যা যদি মত নামক _gem_dec guardউদাহরণস্বরূপ, স্থাপিত হবে _gem_decনামে একটি ফাংশন ঘোষণা _guardশুধু বিরোধিতা guard

দ্রষ্টব্য: এই জাতীয় আচরণ আমার পক্ষে অভ্যাসের বিষয় - আমি সাধারণত এই ধারণার উপর পরিচালনা করি যে শেল ফাংশনগুলি কেবল শেলযথাযথভাবে_namespaceতাদের অনুপ্রবেশ এড়ানোর জন্য কেবল তাদের নিজস্বদখল করা উচিত।namespacecommands

এটি কোনও সর্বজনীন অভ্যাস নয়, যদিও অনুরোধ করার জন্য commandজিজ্ঞাসাবাদীর ব্যবহারে এটি স্পষ্টভাবে প্রমাণিত $1

আরও পরীক্ষা আমাকে নিম্নলিখিত বিশ্বাস করতে পরিচালিত করে:

প্রশ্নকারী শেল ফাংশন চায় যার নাম দেওয়া হয় guard, rspec, or rake, যখন ডাকা হয়, ফাইলটি rubyএকই নামের একটি ফাংশন নতুনভাবে সংকলন করবে বাif ফাইলটি Gemfileউপস্থিত নেই, শেল ফাংশনটি একই নামের ফাংশনটি সম্পাদন করবে ।$PATH if Gemfileruby

এই পূর্বে কাজ হতো না কারণ আমি রদবদল $1উপর ডাকা commandপড়তে:

command _${1}

যার ফলে rubyশেল ফাংশনটি এইভাবে সংকলিত ফাংশনটি সম্পাদন করতে পারে না :

bundle exec $1

আমি আশা করি আপনি দেখতে পাচ্ছেন (শেষ পর্যন্ত আমি যেমন করেছিলাম) মনে হয় যে প্রশ্নকর্তা কেবল commandপরোক্ষভাবে নির্দিষ্ট করার জন্য ব্যবহার করছেন namespaceকারণ একই নামের শেল commandফাংশনটিতে একটি এক্সিকিউটেবল ফাইলকে কল করতে পছন্দ করবেন $PATH

যদি আমার বিশ্লেষণটি সঠিক হয় (যেমনটি আমি আশা করি যে প্রশ্নকারী নিশ্চিত করবেন) তবে এটি:

_${1}() { [ ! -e 'Gemfile' ] && { 
    command $1 "\$@" ; return \$?
    } || bundle exec $1 "\$@"
}

এই শর্তগুলি আরও ভালভাবে ব্যতিক্রম সহকারে সন্তুষ্ট করা উচিত যে guardপ্রম্পটে কল করা কেবলমাত্র$PATH নামমাত্র একটি এক্সিকিউটেবল ফাইল সম্পাদন করার চেষ্টা করবে guardযেখানে _guardপ্রম্পটে কল করা Gemfile'sঅস্তিত্বের জন্য যাচাই করবে এবং তদনুসারে সংকলন করবে বা এক্সিকিউটেবলকে guardসম্পাদন করবে $PATHএইভাবে namespaceসুরক্ষিত এবং কমপক্ষে আমি এটি উপলব্ধি করেই, জিজ্ঞাসকের উদ্দেশ্য এখনও পূরণ হয়েছে।

আসলে, আমাদের শেল ফাংশন সাহসী _${1}()এবং এক্সিকিউটেবল ${PATH}/${1}হয় শুধুমাত্র দুটি উপায়ে আমাদের শেল একটি কল অর্থ বলতে পারল পারেন $1বা _${1}তারপর ব্যবহার commandএ সব ফাংশন মধ্যে এখন সম্পূর্ণরূপে অপ্রয়োজনীয় তৈরি করা হয়। তবুও, আমি এটিকে একইভাবে দু'বার ... একইভাবে দু'বার ভুল করতে পছন্দ করি না বলেই রেখে দিয়েছি।

যদি এটি প্রশ্নকারীর কাছে গ্রহণযোগ্য না হয় এবং তিনি _তার পুরো রূপটি সম্পূর্ণরূপে অগ্রাহ্য করতে পছন্দ করেন তবে তার বর্তমান আকারে, আউটটি সম্পাদনা _underscoreকরা সমস্ত অনুরোধকারীকে তার প্রয়োজনীয়তাগুলি পূরণ করার জন্য করা উচিত যেমন আমি তাদের বুঝতে পারি।

এই পরিবর্তনটি বাদ দিয়ে আমি মূল সিনট্যাক্সের চেয়ে শর্ট-সার্কিট শর্তাবলী ব্যবহার করতে &&এবং / অথবা|| শেল ব্যবহার করতে ফাংশনটি সম্পাদনা করেছি । এইভাবে বিবৃতিটি যদি না থাকে তবে কেবলমাত্র মূল্যায়ন করা হয় ইভেন্টে স্টেটমেন্টটি চালু না রয়েছে তা নিশ্চিত করার জন্য এই সংশোধনটির সংযোজন প্রয়োজন তবে ফাংশনটি 0 ব্যতীত অন্য কোনও কিছু প্রদান করে if/thencommandGemfile$PATHreturn $?bundleGemfileruby $1

সর্বশেষে, আমার নোট করা উচিত যে এই সমাধানটি কেবল পোর্টেবল শেল কনস্ট্রাক্টসগুলি প্রয়োগ করে। অন্য কথায়, এটি পসিক্স সামঞ্জস্যতার দাবি করে যে কোনও শেলের ক্ষেত্রে অভিন্ন ফলাফল তৈরি করবে। যদিও এটা হবে, অবশ্যই, আজেবাজে কথা আমাকে দাবি করার জন্য যে POSIX সামঞ্জস্যপূর্ণ সিস্টেম হ্যান্ডেল করতে হবে ruby bundleডিরেক্টিভের, অন্তত শেল শর্তগুলো কলিং উপর এটা কিনা কলিং শেল হল নির্বিশেষে একই আচরণ করা উচিত shবা dashএছাড়াও আশানুরূপ উপরে ইচ্ছা কাজ (অন্তত অর্ধেক-বিবেকী এ সাহসী shoptsযাহাই হউক না কেন) উভয় bashএবং zsh


আমি আপনার কোডটি ~/.bashrcকল করে রেখেছি . ~/.bashrc, তারপরে এই তিনটি ফাংশনটি কার্যকর করা হবে। আচরণের সাথে পরিবেশের পার্থক্য থাকতে পারে, তাই আমি আমার পরিবেশকে প্রশ্নটিতে যুক্ত করেছি। শেষের লাইনের _guard ; _rspec ; _rakeদরকার কেন তা আমি বুঝতে পারি না। আমি অনুসন্ধানকারীর shiftফাইল অনুসন্ধান করেছি এবং দেখে মনে হচ্ছে এগুলি আমার বর্তমান বোঝার বাইরে।
বিস্ময়কর

আমি কেবল এটি সেখানে রেখেছি যাতে তারা কলযোগ্য হয় show দুঃখিত - আমি এখন একটি প্রতিধ্বনি রেখেছি। সুতরাং আপনি এগুলিকে ফাংশন হিসাবে কল করতে পারেন, যেমন আপনি প্রদর্শন করেছেন।
মাইকজার্ভ 20:38

@ টেটসু - এটি কি এখন আরও ভাল বোঝায়?
মাইক্রজারভ

আমি আপনার উত্তরটি 3 বার পড়েছি, তবে সত্যই বলেছি ব্যাখ্যাটি বোঝার জন্য আমার আরও জ্ঞানের প্রয়োজন। যদিও আমি আপনাকে খুব কৃতজ্ঞ, আরও অভিজ্ঞতা পেলে আমি এটি আবার পড়ব।
ironsand

@ তেতসু সম্ভবত এখন আরও পরিষ্কার হয়ে গেছে ...? আমি মনে করি আমি বুঝতে পেরেছি এবং এখনই সংশোধন করেছি, যা আমি আগে করেছি। আপনি দয়া করে আমাকে জানান।
মাইকসার্ভ

2
function threeinone () {
    local var="$1"
    if [ $# -ne 1 ]; then
        return 1
    fi
    if ! [ "$1" = "guard" -o "$1" = "rspec" -o "$1" = "rake" ]; then
        return 1
    fi
    shift
    if [ -e 'Gemfile' ]; then
        bundle exec "$var" "$@"
    else
        command "$var" "$@"
    fi
}

threeinone guard
threeinone rspec
threeinone rake
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.