_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's
cô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 exec
mụ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 .bashrc
tệ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ộ 3
mô tả tệp ở trên để tránh stdin, stdout, and stderr, or <&0 >&1 >&2
thó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à <input
hoặc>output
với [optional num]<file
hoặc [optional num]>file
chuyể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 device
tệ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-document
chỉ 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-document
thì vỏ sẽ đánh giá nó cho vỏ $expansion
như:
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 FUNC
giớ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ó $expansions
trướ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 -dash
trướ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ỏ $expansion
Tôi sẽ đề nghị here-document
vì 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.file
chỉ 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 $#
shift
loại bỏ nhiều positional parameters
như bạn chỉ định và bắt đầu từ $1
những gì còn lại. Vì vậy, nếu tôi gọi _gem_dec one two three
tại dấu nhắc _gem_dec's $1 $2 $3
tham số vị trí sẽ one two three
và 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 one
vàtwo
sẽ được shift
ed đi, giá trị của $1
sẽ thay đổi để three
và $#
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.sh
tạ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.sh
sẽ 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 device
tậ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-document
tậ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ì _guard
chứ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ự $ENV
trớ 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<<-FUNC
dạ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 guard
ví dụ, sẽ cho kết quả trong _gem_dec
khai báo một hàm có tên _guard
như 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ênnamespace
vỏcommands
thí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 ruby
hàm có cùng tên if
mà tệp Gemfile
tồn tại trong $PATH
OR
if Gemfile
không tồn tại, hàm shell sẽ thực thi ruby
hà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 $1
gọi bằng cách command
đọc:
command _${1}
Điều này sẽ không dẫn đến việc thực thi ruby
hà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 namespace
vì command
sẽ thích gọi một tệp thực thi trong $PATH
mộ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 guard
tạ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 guard
trong khi gọi _guard
tại dấu nhắc sẽ kiểm tra Gemfile's
sự tồn tại và biên dịch tương ứng hoặc thực thi lệnh thực guard
thi trong $PATH
. Theo cách namespace
nà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 $1
hoặc _${1}
sau đó việc sử dụng command
hà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 _underscore
ra 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/then
cú pháp ban đầu . Theo cách này, command
câu lệnh chỉ được đánh giá ở tất cả nếu Gemfile
không có $PATH
. Tuy nhiên, sửa đổi này yêu cầu bổ sung return $?
tuy nhiên để đảm bảo bundle
câu lệnh không chạy trong sự kiện Gemfile
không tồn tại nhưng ruby $1
hà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 bundle
chỉ 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à sh
hay dash
. Ngoài ra, những điều trên sẽ hoạt động như mong đợi ( shopts
dù sao cũng ít nhất là nửa chừng ) trong cả hai bash
và zsh
.
for loop?
tôi, các biến được khai báofor loop
nó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ự.