_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()và_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 onevàtwo sẽ được shifted đi, giá trị của $1sẽ thay đổi để threevà $#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 namespacevì commandsẽ 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 bashvà zsh.
for loop?tôi, các biến được khai báofor 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ự.