Câu trả lời:
Đặt IFS
thành :
để phân chia giá trị của PATH
dấu hai chấm. Nếu bạn find
có -quit
hành động và -maxdepth
chính (ví dụ FreeBSD, OSX, GNU), bạn biết lệnh sẽ tồn tại và bạn không quan tâm đến mã trả về của lệnh, bạn có thể sử dụng lệnh này:
pattern='libreoffice?.?'
IFS=:; find $PATH -maxdepth 1 -type f -name "$pattern" -exec {} \; -quit; unset IFS
Điều này không cung cấp một cách dễ dàng để báo cáo xem lệnh đã được tìm thấy chưa. Hơn nữa, để mạnh mẽ hơn, hãy tắt Globing trong trường hợp giá trị của PATH
các ký tự đại diện. Ngoài ra, có thể có một thành phần trống trong PATH
có nghĩa là thư mục hiện tại (nhưng lời khuyên của tôi là sử dụng .
thay thế). Các mã dưới đây chăm sóc tất cả các biến chứng.
pattern='libreoffice?.?'
case $PATH in
:*) directories=.$PATH;;
*::*) directories=${PATH%%::*}:.:${PATH#*::};;
*:) directories=$PATH.;;
*) directories=$PATH;;
esac
set -f; IFS=:
cmd=
for d in $directories; do
set +f
for x in "$d"/$pattern; do
if [ -x "$x" ] && ! [ -d "$x" ]; then
cmd=$x
break
fi
done
if [ -n "$cmd" ]; then break; fi
done
set +f; unset IFS
if [ -z "$cmd" ]; then
echo 1>&2 "$pattern: not found in PATH"
exit 127
else
exec "$cmd"
fi
Nếu bạn tình cờ sử dụng zsh (trái ngược với plain sh, bash, ksh, thì), việc tạo ra một giải pháp mạnh mẽ sẽ đơn giản hơn rất nhiều.
pattern='libreoffice?.?'
matches=($^path/$~pattern(N.*[1]))
if ((!#matches)); then
$matches[1]
else
echo 1>&2 "$pattern: not found in PATH"
exit 127
fi
::
giả định .
cho ::
là chỉ di sản tuân thủ QTI anyway - và các ứng dụng mới hơn nên làm như vậy một giả định.
find $PATH -type f -perm /u=x,g=x,o=x -prune -name "$@" | head -n1
$PATH
(và vâng, điều đó xảy ra).
"$1"
, vì chỉ có thể có một mẫu ở đây.
Với zsh
:
$commands[(i)libreoffice?.?]
Trong zsh
, $commands
là một mảng kết hợp đặc biệt có các khóa là tên lệnh và giá trị đường dẫn của chúng.
i
ở trên là một cờ chỉ số mảng cho biết zsh
để khớp mẫu với các khóa mảng và trả về khóa khớp đầu tiên.
Các phần tử của mảng kết hợp không theo bất kỳ thứ tự cụ thể nào, do đó, khóa khớp đầu tiên sẽ không nhất thiết phải là đầu tiên xảy ra $PATH
. Nếu bạn muốn libreoffice
có số phiên bản lớn nhất, bạn có thể làm:
${${(nO)${commands[(I)libreoffice?.?]}}[1]}
Các I
cờ subscript mở rộng tới tất cả các phím phù hợp. Chúng tôi sử dụng các cờ mở rộng tham sốn
(sắp xếp bằng số) và O
(thứ tự ngược) để sắp xếp danh sách đó từ số phiên bản lớn nhất đến nhỏ nhất, sau đó chọn cái đầu tiên.[1]
Xem thêm:
whence -m 'libreoffice?.?'
để tìm đường dẫn của các lệnh tương ứng.
$commands[cmd]
là lần đầu tiên cmd
trong $PATH
, nó chỉ rằng nếu bạn có /bin/libreoffice4.2
và /usr/bin/libreoffice4.1
, giải pháp đầu tiên có thể trở lại rất tốt libreoffice4.1
ngay cả khi /bin
là trước khi /usr/bin
vào $PATH
). Trong đó zsh
, $path
mảng được gắn với $PATH
vô hướng (một tính năng mượn từ csh)
$path
có thể thay đổi mà không thay đổi $PATH
? Dù sao, tôi biết rất ít về điều đó - chỉ có điều chúng là những thứ riêng biệt. Không - đã thử nó - thay đổi $path
cũng thay đổi $PATH
.
$path
và $PATH
bị trói. Bất kỳ sửa đổi của một cái được phản ánh trong cái khác. Xem mô tả của typeset -T
.
$PATH
và $path
. Đối số thứ ba tùy chọn là một dấu tách ký tự đơn sẽ được sử dụng để nối các phần tử của mảng để tạo thành vô hướng; nếu vắng mặt, một dấu hai chấm được sử dụng. zsh
chắc chắn có một số thứ gọn gàng, chỉ là nó làm quá nhiều đến nỗi tôi thường bị lạc. Đó là lý do tại sao, đối với những thứ phức tạp, tôi đã học được cách thích ksh93
tốt hơn nhiều - nó đơn giản hơn, như tôi nghĩ. Nhưng đó chỉ là sở thích.
Đây là một thay thế đơn giản hơn:
$(compgen -c libreoffice)
Nó giả định bash, và giả sử chỉ có một libreoffice*
cài đặt.
Nó mô phỏng những gì hoàn thành tab bash sẽ làm nếu bạn gõ libreoffice
Tab.
Nếu bạn cố tình loại trừ libreoffice
mà không có số phiên bản và muốn xử lý sự tồn tại của nhiều phiên bản, hãy thử:
run_libreoffice() {
compgen -c libreoffice |
while read -r exe; do
case "$exe" in libreoffice?.?)
"$exe" "$@"
return
;;
esac
done
}
run_libreoffice "$@"
Câu case
lệnh làm cho nó chỉ khớp libreoffice?.?
và chúng tôi lặp lại kết quả, chỉ chạy cái đầu tiên.
bash
- nó giả định một kết quả đầy đủ trên mỗi dòng và không có $IFS
hoặc có các ký tự toàn cầu trong kết quả.
compgen
sau đó mẫu case
không lý tưởng.
Bạn có thể sử dụng lệnh "tìm" như sau
find ./ -name "libreoffice?.?"
$PATH
, không phải$PWD
POSIXly, khi tìm kiếm một lệnh$PATH
thực thi , bạn nên sử dụng :command
command -v
Nếu được gọi là không có công tắc, command
sẽ gọi lệnh_name , nhưng bạn vẫn cần một đường dẫn được giải quyết đầy đủ để giải quyết hoàn toàn shell shell .
Vì vậy, để làm điều đó, bạn nên kiểm tra toàn cầu theo từng :
phần tử được phân tách bằng dấu hai chấm $PATH
theo thứ tự, như spec'd :
PATH
:
. Khi một tiền tố có độ dài khác không được áp dụng cho tên tệp này, một dấu gạch chéo sẽ được chèn vào giữa tiền tố và tên tệp. Tiền tố có độ dài bằng không là một tính năng kế thừa cho biết thư mục làm việc hiện tại. Nó xuất hiện dưới dạng hai dấu hai chấm liền kề ::
, như một dấu hai chấm ban đầu trước phần còn lại của danh sách hoặc dưới dạng dấu hai chấm sau phần còn lại của danh sách. Một ứng dụng tuân thủ nghiêm ngặt sẽ sử dụng một tên đường dẫn thực tế (như .
) để thể hiện thư mục làm việc hiện tại trongPATH
. Danh sách sẽ được tìm kiếm từ đầu đến cuối, áp dụng tên tệp cho từng tiền tố, cho đến khi tìm thấy tệp thực thi có tên được chỉ định và quyền thực thi phù hợp. Nếu tên đường dẫn được tìm kiếm chứa dấu gạch chéo, tìm kiếm thông qua tiền tố đường dẫn sẽ không được thực hiện. Nếu tên đường dẫn bắt đầu bằng dấu gạch chéo, đường dẫn đã chỉ định được giải quyết (xem Độ phân giải tên đường dẫn ) . Nếu PATH
không được đặt hoặc được đặt thành null, tìm kiếm đường dẫn được xác định theo thực hiện .Bạn có thể tách ra $PATH
thành một trường cho mỗi $PATH
thành phần $IFS
, nhưng bạn chỉ có thể thực hiện việc này một cách an toàn nếu bạn cũng set -f
mở rộng việc mở rộng bị vô hiệu hóa trong khi bạn thực hiện, trong trường hợp một thành phần $PATH
có chứa bất kỳ [?*
ký tự nào, vì việc tạo tên tệp (đọc: globalbing) xảy ra sau trường - tách theo thứ tự mở rộng của shell.
Theo cách đó, nói chung, cách duy nhất thực sự mạnh mẽ để thực hiện POSIXly này là trước tiên mở rộng danh sách trường $IFS
trong khi việc tạo tên tệp bị vô hiệu hóa và bằng cách nào đó lưu kết quả mảng đó, sau đó một lần nữa kích hoạt tạo tên tệp và vô hiệu hóa tách trường ( để bạn có thể lưu trữ và mở rộng mô hình toàn cầu của mình mà không được trích dẫn trong một biến mà không có bất kỳ nguy hiểm nào về việc nó bị ảnh hưởng $IFS
trước tiên) và lần lượt vận hành theo kết quả.
May mắn thay, điều này không quá khó:
g='libreoffice?.?'
( IFS=:; set-f #split on :; disable filename gen
set +f -- $PATH #store split array, enable filename gen after
IFS= #disable field splitting
for p do for x in ${p:+"$p/"$g}
do command -v "$x"
done;done
) #done in subshell to avoid affecting current shell
Các bit cuối cùng không được bình luận vì chúng được giải thích tốt hơn ở đây.
for p do
- for
Vòng lặp đầu tiên lặp lại gán $p
giá trị của từng tham số vị trí trong mảng của shell"$@"
, là set
phần mở rộng $PATH
phân chia trường được chia cho các :
dấu hai chấm.
for x in ${p:+"$p/"$g}
- for
Vòng lặp bên trong lặp đi lặp lại $x
cho mỗi (nếu có) của các mở rộng của ${p:+"$p/"$g}
- nghĩa là, nó lặp đi lặp lại không có gì cả trừ khi $p
cả hai được đặt và không null. Đây là quan trọng bởi vì ::
hoặc một hàng đầu :
sẽ chia ra các lĩnh vực null, và, theo cách này, hai vòng nghiêm ngặt phù hợp với các $PATH
đặc tả (như trích dẫn ở trên) bằng cách không thừa nhận zero-length lĩnh vực.
Điều quan trọng là do cách $PATH
phân chia trong khi việc tạo tên tệp bị vô hiệu hóa và "$p/"$g
được giải quyết trong khi được bật nhưng tính năng tách trường đã bị vô hiệu hóa, không có yếu tố nào trong đó $PATH
có thể mở rộng sang bất cứ thứ gì ngoài trường của chúng như bị tách ra :
(bất kể các ký tự khác là gì có thể chứa) và không thể có bất kỳ giá trị nào để "$p/"$g
mở rộng sang bất cứ thứ gì ngoại trừ chính nó hoặc bất kỳ tên tệp nào mà nó có thể khớp (bất kể ký tự nào trong $p
hoặc ngoài các ký tự khớp mẫu $g
có thể chứa) .
command
- và command
chỉ in lần cuối để chuẩn hóa các giá trị $x
đó là các lệnh thực thi.
Đây là toàn bộ điều được gói trong một hàm shell, với khả năng bổ sung để xử lý nhiều $g
đối số:
glob_path()(
for g do IFS=:; set -f
set +f -- ${PATH:-.}; IFS=
for p do for x in ${p:+"$p/"$g}
do command -v "$x"
done; done; done
)
Điều đó sẽ giúp bạn có một danh sách các tệp thực thi khớp với từng đối số mà bạn đưa nó in một dòng trên mỗi dòng được sắp xếp theo thứ tự đối số, sau đó theo $PATH
mức độ ưu tiên và cuối cùng theo thứ tự sắp xếp địa phương của bạn. Nó không nên thất bại trên các dòng mới hoặc các ký tự đặc biệt trong tên đường dẫn hoặc các mẫu hình cầu mà bạn trao cho nó. Nó có thể được thay đổi (xem lịch sử chỉnh sửa ở đây) để cư trú trung thực "$@"
mảng shell tại một tham số thực thi cho mỗi tham số vị trí.
Bạn cũng có thể thay đổi nó để thực hiện trận đấu toàn cầu thành công đầu tiên và bỏ tìm kiếm đối số toàn cầu cụ thể đó và chuyển sang tiếp theo nếu lệnh thoát thành công như ...
glob_path()(
for g do IFS=:; set -f
set +f -- ${PATH:-.}; IFS=
for p do for x in ${p+:"$p/"$g}
do command -v "$x" &&
command "$x" &&
break 2
done; done; done
)
Đây là một phương tiện di động POSIX để tìm trận đấu toàn cầu của bạn, nhưng một số vỏ thậm chí không cần for g...
vòng lặp thứ hai . Trong bash
, ví dụ, điều này cũng sẽ hoạt động như ...
glob_path()(
for g do IFS=:; set -f
set +f -- ${PATH:-.}; IFS=
for p do command -v ${p:+"$p/"$g}
done; done
)
... vẫn liệt kê mọi đối sánh toàn cầu có thể thực thi cho tất cả các đối số và cho từng thành phần $PATH
bất kể bất kỳ yếu tố nào hoặc các mẫu hình cầu có chứa dòng mới hoặc ký tự đặc biệt, v.v.
Bạn có thể sử dụng nó như:
glob_path '?sh' '??sh' #I'd rather not install libreoffice
... mà cho tôi in ...
/usr/bin/ksh
/usr/bin/rsh
/usr/bin/ssh
/usr/bin/zsh
/usr/local/bin/dash
/usr/local/bin/yash
/usr/bin/bash
/usr/bin/bssh
/usr/bin/chsh
/usr/bin/dash
/usr/bin/mksh
/usr/bin/posh
/usr/bin/slsh
/usr/bin/yash
Như đã thảo luận, glob_path
hàm diễn giải $PATH
biến môi trường vì thông số kỹ thuật nêu rõ một ứng dụng tuân thủ nghiêm ngặt bằng cách bỏ qua các :
lần xuất hiện hàng đầu, theo dõi hoặc liên tiếp trong $PATH
giá trị của nó, nhưng nó diễn giải một cách trống hoặc không đặt $PATH
thành có nghĩa .
.
Nếu bạn thực sự muốn tính năng cũ đó, chức năng có thể được viết:
glob_path()(
for g do IFS=:; set -f
set +f -- ${0+$PATH:}; IFS=
for p do for x in "${p:-.}/"$g
do command -v "$x"
done; done; done
)
... sẽ diễn giải tất cả các lần xuất hiện của dấu đầu dòng, dấu hoặc hai :
dấu hai chấm liên tiếp .
(và do đó tìm kiếm .
nhiều lần khi một $PATH
thành phần có độ dài bằng không xuất hiện trong $PATH
giá trị) , nhưng điều đó không tuân thủ nghiêm ngặt.
$PATH
chứa các thành phần trống. Ngoài ra, lưu ý rằng IFS xem xét: như một dấu phân cách, không phải dấu phân cách như bạn cần xử lý $ PATH (giá trị của không $PATH
giống /bin:/usr/bin:
nhau)
$PATH
thành phần có thể chứa :
? Dù sao; Tôi đã sửa ::
điều.
IFS=:; $PATH
tách /bin:/usr/bin:
thành (/bin /usr/bin)
các vỏ POSIX, không (/bin /usr/bin '')
(ngoại trừ zsh
trong mô phỏng sh, yash, pdksh và các phiên bản cũ hơn của mksh)
/bin:
có nghĩa là tìm kiếm trong / bin và thư mục hiện tại. Đối với một $ PATH chưa đặt , phụ thuộc vào trình bao, hãy kiểm tra câu trả lời của tôi về lý do tại sao không sử dụng câu hỏi nào . Xem @Gilles 'để biết phương pháp đúng hơn.
::
có thể xảy ra nhiều lần$PATH
. Bạn có thể muốn xem xét trường hợp$PATH
được đặt nhưng cũng trống (thay thế bằng.
).