Cách xác định hàm bash tương tự cùng một lúc


10

Tôi có các chức năng này trong ~/.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
}

Như bạn thấy các chức năng này rất giống nhau. Tôi muốn xác định 3 chức năng này cùng một lúc. Có cách nào để làm cho nó?

Môi trường

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

Câu trả lời:


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[@]}" "$@"
}

Các cảnh báo thông thường về evaláp dụng.


Có phải nó không bị ăn bởi ý for loop?tôi, các biến được khai báo for loopnói chung biến mất - tôi sẽ mong đợi các hàm giống nhau vì những lý do tương tự.
mikeerv

Điều gì làm cho bạn nghĩ? bash -c 'for i in 1; do :; done; echo $i'=> 1. Các typechương trình rõ ràng rằng các chức năng tồn tại bên ngoài phạm vi của vòng lặp.
Adrian Frühwirth

1
@mikeerv Ngay cả với bashphạm vi động của tất cả những gì bạn có thể nhận được là một localbiến cục bộ theo phạm vi của toàn bộ hàm , các biến chắc chắn không "biến mất" sau một vòng lặp. Trong thực tế, vì không có chức năng liên quan ở đây nên thậm chí không thể xác định một localbiến trong trường hợp này.
Adrian Frühwirth

Phải - cục bộ cho vòng lặp for - chúng nằm trong phạm vi cục bộ. Chúng biến mất ngay sau khi vỏ vòng lặp cha mẹ của chúng làm. Điều này không xảy ra ở đây?
mikeerv

Không, như tôi vừa giải thích, không có khái niệm nào như "cục bộ cho vòng lặp for" trong kịch bản shell và bài đăng của tôi và ví dụ trong nhận xét của tôi ở trên cho thấy rõ điều đó.
Adrian Frühwirth

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."

Ý chí trên . source /dev/fd/3được đưa vào _gem_dec()hàm mỗi khi nó được gọi là here-document. _gem_dec'scông việc chỉ được đánh giá trước là nhận một tham số và đánh giá trước nó là cả bundle execmục tiêu và là tên của chức năng mà nó được nhắm mục tiêu.

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

Trong trường hợp trên, tôi không nghĩ rằng có thể có bất kỳ rủi ro.

Nếu khối mã ở trên được sao chép vào một .bashrctệp, không chỉ các hàm shell _guard(), _rspec()_rake() được khai báo khi đăng nhập, mà _gem_dec()hàm này cũng sẽ có sẵn để thực thi bất cứ lúc nào tại dấu nhắc shell của bạn (hoặc nếu không) và vì vậy các hàm templated mới có thể được khai báo bất cứ khi nào bạn muốn chỉ với:

_gem_dec $new_templated_function_name

Và cảm ơn @Andrew đã cho tôi thấy những thứ này sẽ không bị ăn bởi for loop.

NHƯNG BẰNG CÁCH NÀO?

Tôi sử dụng bộ 3mô tả tệp ở trên để tránh stdin, stdout, and stderr, or <&0 >&1 >&2thói quen - mặc dù, đó cũng là trường hợp đối với một số biện pháp phòng ngừa mặc định khác mà tôi thực hiện ở đây - vì chức năng kết quả rất đơn giản, thực sự không cần thiết. Đó là thực hành tốt, mặc dù. Gọi shift $#là một trong những biện pháp phòng ngừa không cần thiết.

Tuy nhiên, khi một tệp được chỉ định là <inputhoặc>output với [optional num]<filehoặc [optional num]>filechuyển hướng , hạt nhân sẽ đọc nó vào một bộ mô tả tệp, có thể được truy cập thông qua các character devicetệp đặc biệt trong /dev/fd/[0-9]*. Nếu [optional num]specifier bị bỏ qua, thì 0<fileđược giả sử cho đầu vào và 1>fileđầu ra. Xem xét điều này:

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

Và bởi vì a here-documentchỉ là một phương tiện để mô tả một tệp nội tuyến trong một khối mã, khi chúng tôi thực hiện:

<<'HEREDOC'
[$CODE]
HEREDOC

Chúng tôi cũng có thể làm:

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

Với một sự phân biệt rất quan trọng . Nếu bạn không "'\quote'"những <<"'\LIMITER"'của một here-documentthì vỏ sẽ đánh giá nó cho vỏ $expansionnhư:

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

Vì vậy, ví _gem_dec(), những 3<<-FUNC here-documentđược đánh giá là một tập tin trên đầu vào, giống như nó sẽ là nếu nó là 3<~/some.file ngoại trừ điều đó vì chúng tôi rời khỏi FUNCgiới hạn tự do của dấu ngoặc kép, nó là lần đầu tiên đánh giá về $expansion.Điều quan trọng về vấn đề này là nó là đầu vào, ý nghĩa nó chỉ tồn tại _gem_dec(),nhưng nó cũng được đánh giá trước khi _gem_dec()hàm chạy vì shell của chúng ta phải đọc và đánh giá nó $expansionstrước khi chuyển nó thành đầu vào.

Hãy làm guard,ví dụ:

_gem_dec guard

Vì vậy, đầu tiên shell phải xử lý đầu vào, có nghĩa là đọc:

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

Vào mô tả tập tin 3 và đánh giá nó để mở rộng shell. Nếu tại thời điểm này bạn đã chạy:

cat /dev/fd/3

Hoặc là:

cat <&3

Vì cả hai lệnh tương đương bạn đều thấy *:

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

... Trước đây, bất kỳ mã nào trong hàm đều thực thi. Đây là chức năng của <input, sau tất cả. Để biết thêm ví dụ, xem câu trả lời của tôi cho một câu hỏi khác ở đây .

(* Về mặt kỹ thuật thì điều này không hoàn toàn đúng. Bởi vì tôi sử dụng hàng đầu -dashtrước here-doc limiter, tất cả những điều trên sẽ được chứng minh trái. Nhưng tôi đã sử dụng -dashđể tôi có thể <tab-insert>đọc được ngay từ đầu nên tôi sẽ không loại bỏ <tab-inserts>trước cung cấp cho bạn để đọc ...)

Phần hay nhất về điều này là phần trích dẫn - lưu ý rằng các '"trích dẫn vẫn còn và chỉ các \trích dẫn đã bị tước. Nó có lẽ là vì lý do này nhiều hơn bất kỳ khác rằng nếu bạn có đến hai lần đánh giá lại một vỏ $expansionTôi sẽ đề nghị here-documentvì có dấu ngoặc kép là nhiều dễ dàng hơn eval.

Dù sao, bây giờ đoạn mã trên giống hệt như một tập tin được cung cấp giống như 3<~/heredoc.filechỉ chờ _gem_dec()chức năng hoạt động và chấp nhận đầu vào của nó /dev/fd/3.

Vì vậy, khi chúng tôi bắt _gem_dec()đầu, điều đầu tiên tôi làm là ném tất cả các tham số vị trí, bởi vì bước tiếp theo của chúng tôi là mở rộng shell được đánh giá hai lần và tôi không muốn bất kỳ tham số nào $expansionsđược hiểu là bất kỳ $1 $2 $3...tham số hiện tại nào của tôi . Vì vậy, tôi:

shift $#

shiftloại bỏ nhiều positional parametersnhư bạn chỉ định và bắt đầu từ $1những gì còn lại. Vì vậy, nếu tôi gọi _gem_dec one two threetại dấu nhắc _gem_dec's $1 $2 $3tham số vị trí sẽ one two threevà tổng số vị trí hiện tại, hoặc $#sẽ là 3. Nếu tôi sau đó được gọi là shift 2,các giá trị của onetwo sẽ được shifted đi, giá trị của $1sẽ thay đổi để three$#sẽ mở rộng tới 1. Vì vậy, shift $#chỉ ném tất cả chúng đi. Làm điều này là phòng ngừa nghiêm ngặt và chỉ là một thói quen tôi đã phát triển sau khi làm điều này trong một thời gian. Đây là một sự (subshell)dàn trải một chút cho rõ ràng:

( 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

Dù sao, bước tiếp theo là nơi phép màu xảy ra. Nếu bạn . ~/some.shtại dấu nhắc shell thì tất cả các hàm và biến môi trường được khai báo ~/some.shsẽ có thể gọi được tại dấu nhắc shell của bạn. Điều này cũng đúng ở đây, ngoại trừ chúng tôi . source các character devicetập tin đặc biệt cho bộ mô tả tập tin của chúng tôi, hoặc . /dev/fd/3- đó là nơi chúng tôi here-documenttập tin trong dòng đã được pathed - và chúng tôi đã tuyên bố chức năng của chúng tôi. Và đó là cách nó hoạt động.

_guard

Bây giờ làm bất cứ điều gì _guardchức năng của bạn là phải làm.

Phụ lục:

Một cách tuyệt vời để nói lưu vị trí của bạn:

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

BIÊN TẬP:

Khi tôi trả lời câu hỏi này lần đầu tiên, tôi tập trung nhiều hơn vào vấn đề khai báo một shell function()có khả năng khai báo các hàm khác sẽ tồn tại trong sự $ENVtrớ trêu hiện tại so với những gì người hỏi sẽ làm với các hàm liên tục đã nói. Kể từ đó, tôi nhận ra rằng giải pháp ban đầu được trao cho tôi có 3<<-FUNCdạng:

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

Có thể sẽ không làm việc như mong đợi cho hỏi vì tôi đặc biệt thay đổi tên chức năng tường thuật từ $1để _${1}đó, nếu gọi như _gem_dec guardví dụ, sẽ cho kết quả trong _gem_deckhai báo một hàm có tên _guardnhư trái ngược với chỉ guard.

Lưu ý: Những hành động đó là một vấn đề của thói quen đối với tôi - Tôi thường hoạt động trên giả định rằng chức năng vỏ nên chiếm chỉ riêng của họ_namespaceđể tránh sự xâm nhập của họ trênnamespacevỏcommandsthích hợp.

Tuy nhiên, đây không phải là một thói quen phổ biến, như được chứng minh trong cách sử dụng của người hỏi commandđể gọi $1.

Kiểm tra thêm khiến tôi tin vào những điều sau đây:

Người hỏi muốn các hàm shell có tên guard, rspec, or rakeđó, khi được gọi, sẽ biên dịch lại một rubyhàm có cùng tên ifmà tệp Gemfiletồn tại trong $PATH OR if Gemfile không tồn tại, hàm shell sẽ thực thi rubyhàm cùng tên.

Điều này sẽ không hoạt động trước đây bởi vì tôi cũng đã thay đổi cách $1gọi bằng cách commandđọc:

command _${1}

Điều này sẽ không dẫn đến việc thực thi rubyhàm mà hàm shell được biên dịch là:

bundle exec $1

Tôi hy vọng bạn có thể thấy (như cuối cùng tôi đã làm) rằng có vẻ như người hỏi chỉ đang sử dụng commandđể gián tiếp chỉ định namespacecommandsẽ thích gọi một tệp thực thi trong $PATHmột hàm shell cùng tên.

Nếu phân tích của tôi là chính xác (như tôi hy vọng người hỏi sẽ xác nhận) thì đây:

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

Nên đáp ứng tốt hơn các điều kiện đó ngoại trừ việc gọi guardtại dấu nhắc sẽ chỉ cố gắng thực thi một tệp thực thi $PATHđược đặt tên guardtrong khi gọi _guardtại dấu nhắc sẽ kiểm tra Gemfile'ssự tồn tại và biên dịch tương ứng hoặc thực thi lệnh thực guardthi trong $PATH. Theo cách namespacenày được bảo vệ và, ít nhất là khi tôi nhận thấy nó, ý định của người hỏi vẫn được thực hiện.

Trong thực tế, giả sử hàm shell của chúng ta _${1}()và hàm thực thi ${PATH}/${1}là hai cách duy nhất mà shell của chúng ta có thể diễn giải một cuộc gọi đến $1hoặc _${1}sau đó việc sử dụng commandhàm này hoàn toàn không cần thiết. Tuy nhiên, tôi vẫn để nó như tôi không muốn mắc lỗi tương tự hai lần ... liên tiếp.

Nếu điều này không được chấp nhận đối với người hỏi và anh ấy / cô ấy muốn loại bỏ _hoàn toàn thì ở dạng hiện tại, việc chỉnh sửa _underscorera sẽ là tất cả những gì người hỏi cần làm để đáp ứng yêu cầu của anh ấy / cô ấy khi tôi hiểu họ.

Ngoài sự thay đổi đó, tôi cũng đã chỉnh sửa hàm để sử dụng &&và / hoặc|| shell điều kiện ngắn mạch thay vì if/thencú pháp ban đầu . Theo cách này, commandcâu lệnh chỉ được đánh giá ở tất cả nếu Gemfilekhông có $PATH. Tuy nhiên, sửa đổi này yêu cầu bổ sung return $?tuy nhiên để đảm bảo bundlecâu lệnh không chạy trong sự kiện Gemfilekhông tồn tại nhưng ruby $1hàm trả về bất cứ thứ gì khác 0.

Cuối cùng, tôi cần lưu ý rằng giải pháp này chỉ thực hiện các cấu trúc vỏ di động. Nói cách khác, điều này sẽ tạo ra kết quả giống hệt nhau trong bất kỳ shell nào yêu cầu khả năng tương thích POSIX. Tất nhiên, trong khi điều đó sẽ vô nghĩa đối với tôi khi yêu cầu mọi hệ thống tương thích POSIX phải xử lý ruby bundlechỉ thị, ít nhất là các mệnh lệnh shell yêu cầu nó phải hành xử giống nhau bất kể vỏ gọi là shhay dash. Ngoài ra, những điều trên sẽ hoạt động như mong đợi ( shoptsdù sao cũng ít nhất là nửa chừng ) trong cả hai bashzsh.


Tôi đặt mã của bạn vào ~/.bashrcvà gọi . ~/.bashrc, sau đó ba hàm này được thực thi. Có thể hành vi khác nhau theo môi trường, vì vậy tôi đã thêm môi trường của mình vào câu hỏi. Ngoài ra tôi không thể hiểu tại sao dòng cuối cùng _guard ; _rspec ; _rakelà cần thiết. Tôi đã tìm kiếm shiftvà mô tả tập tin, có vẻ như những điều này nằm ngoài sự hiểu biết hiện tại của tôi.
ironsand 20/03/14

Tôi chỉ cần đặt nó ở đó để cho thấy họ có thể gọi được. Xin lỗi - tôi đặt một tiếng vang bây giờ. Vì vậy, bạn có thể gọi chúng là các chức năng, như bạn đã chứng minh.
mikeerv

@Tetsu - bây giờ nó có ý nghĩa hơn không?
mikeerv

Tôi đã đọc qua câu trả lời của bạn 3 lần, nhưng thành thật mà nói tôi cần thêm kiến ​​thức để hiểu lời giải thích. Mặc dù tôi rất biết ơn bạn, tôi sẽ đọc lại khi tôi có thêm kinh nghiệm.
ironsand 21/03 '

@Tetsu Có lẽ bây giờ rõ ràng hơn ...? Tôi nghĩ rằng tôi đã nhận ra, và bây giờ đã sửa chữa, một lỗi tôi đã làm trước đây. Xin vui lòng cho tôi biết, nếu bạn muốn.
mikeerv

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.