thực hiện một lệnh trong $ PATH khớp với ký tự đại diện


7

Tôi muốn tìm và thực thi một lệnh trong hiện tại $PATHphù hợp với ký tự đại diện này libreoffice?.?(ví dụ. libreoffice4.0, libreoffice4.3Vv)

EDIT: nếu tìm thấy nhiều trận đấu, bạn có thể chọn ngẫu nhiên một trận đấu.

Tôi thích một giải pháp tuân thủ POSIX.

Câu trả lời:


3

Đặt IFSthành :để phân chia giá trị của PATHdấu hai chấm. Nếu bạn find-quithành động và -maxdepthchí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 PATHcác ký tự đại diện. Ngoài ra, có thể có một thành phần trống trong PATHcó 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

Nói đúng ra, ::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 .).
Stéphane Chazelas

@ StéphaneChazelas - mỗi cuộc nói chuyện trước đó của chúng tôi ::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.
mikeerv

Tôi thích giải pháp oneliner, nhưng với các công tắc tương thích posix:find $PATH -type f -perm /u=x,g=x,o=x -prune -name "$@" | head -n1
eadmaster 22/215

@eadmaster Bạn đang đệ quy vào các thư mục con vào các thư mục trong $PATH(và vâng, điều đó xảy ra).
Gilles 'SO- ngừng trở nên xấu xa'

2
@eadmaster Thực hiện điều đó "$1", vì chỉ có thể có một mẫu ở đây.
Gilles 'SO- ngừng trở nên xấu xa'

2

Với zsh:

$commands[(i)libreoffice?.?]

Trong zsh, $commandslà 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 libreofficecó số phiên bản lớn nhất, bạn có thể làm:

${${(nO)${commands[(I)libreoffice?.?]}}[1]}

Các Icờ 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.


1
@mikeserv, nó báo cáo chìa khóa, vì vậy tên lệnh, tra cứu $ PATH bình thường sẽ gọi là người đầu tiên trong $ PATH cho phiên bản lớn nhất của libreoffice (và $commands[cmd]là lần đầu tiên cmdtrong $PATH, nó chỉ rằng nếu bạn có /bin/libreoffice4.2/usr/bin/libreoffice4.1, giải pháp đầu tiên có thể trở lại rất tốt libreoffice4.1ngay cả khi /binlà trước khi /usr/binvào $PATH). Trong đó zsh, $pathmảng được gắn với $PATHvô hướng (một tính năng mượn từ csh)
Stéphane Chazelas

Nhưng tôi nghĩ $pathcó 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 $pathcũng thay đổi $PATH.
mikeerv 23/2/2015

@mikerserv, không, $path$PATHbị 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.
Stéphane Chazelas

Thú vị ... Hai đối số đầu tiên là tên của một vô hướng và một tham số mảng (theo thứ tự đó) sẽ được gắn với nhau theo cách $PATH$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. zshchắ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 ksh93tốt hơn nhiều - nó đơn giản hơn, như tôi nghĩ. Nhưng đó chỉ là sở thích.
mikeerv 23/2/2015

1

Đâ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õ libreofficeTab.

Nếu bạn cố tình loại trừ libreofficemà 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 caselệ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.


1
Nó giả định nhiều hơn bash- nó giả định một kết quả đầy đủ trên mỗi dòng và không có $IFShoặc có các ký tự toàn cầu trong kết quả.
mikeserv

đó là một lựa chọn thú vị, nhưng nó chỉ giới hạn ở các ký tự đại diện bắt đầu bằng một chuỗi cố định ...
eadmaster 22/215

Hình thức thứ hai không giới hạn ở các chuỗi cố định, nhưng phải chỉ định chuỗi con để compgensau đó mẫu casekhông lý tưởng.
Mikel

0

Bạn có thể sử dụng lệnh "tìm" như sau

find ./ -name "libreoffice?.?"

1
không hoạt động, lưu ý rằng tôi có nghĩa là hệ thống $PATH, không phải$PWD
eadmaster

1
tìm $ (echo $ PATH | sed 's /: / / g') -name "libreoffice ?.?"
ngủ gật

ok, nó hoạt động :)
eadmaster

Để tìm trong tất cả các thư mục $ PATH, chúng ta nên thực hiện tìm kiếm trong tất cả các thư mục đó. Điều này có nghĩa như thế này: 'tìm dir1 dir2 dir3 -name "libreoffice ?.?"'. Do đó, những gì chúng ta chỉ thay thế dấu chấm phẩy cho khoảng trắng bằng lệnh $ (echo $ PATH | sed 's /: / / g')
ngủ gật

0

POSIXly, khi tìm kiếm một lệnh$PATH thực thi , bạn nên sử dụng :command

  • command -v
    • Viết một chuỗi vào đầu ra tiêu chuẩn cho biết tên đường dẫn hoặc lệnh sẽ được shell sử dụng, trong môi trường thực thi shell hiện tại (xem Môi trường thực thi Shell , để gọi lệnh_name , nhưng không gọi lệnh_name .
    • ... Mặt khác, không có đầu ra nào được ghi và trạng thái thoát sẽ phản ánh rằng tên đó không được tìm thấy.

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 $PATHtheo thứ tự, như spec'd :

  • PATH
    • Biến này sẽ biểu thị chuỗi các tiền tố đường dẫn mà các chức năng và tiện ích nhất định áp dụng khi tìm kiếm tệp thực thi chỉ được biết bởi tên tệp . Các tiền tố sẽ được phân tách bằng dấu hai chấm :. 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 PATHkhô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 $PATHthành một trường cho mỗi $PATHthà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 -fmở 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 $PATHcó 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 $IFStrong 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ô 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 $IFStrướ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- forVòng lặp đầu tiên lặp lại gán $pgiá trị của từng tham số vị trí trong mảng của shell"$@" , là setphần mở rộng $PATHphân chia trường được chia cho các :dấu hai chấm.

  • for x in ${p:+"$p/"$g}- forVòng lặp bên trong lặp đi lặp lại $xcho 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 $pcả 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 $PATHphâ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 đó $PATHcó 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/"$gmở 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 $phoặc ngoài các ký tự khớp mẫu $gcó thể chứa) .

  • command- và commandchỉ 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 $PATHmứ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 $PATHbấ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_pathhàm diễn giải $PATHbiế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 $PATHgiá trị của nó, nhưng nó diễn giải một cách trống hoặc không đặt $PATHthà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 $PATHthành phần có độ dài bằng không xuất hiện trong $PATHgiá trị) , nhưng điều đó không tuân thủ nghiêm ngặt.


tôi thích câu trả lời của dss ngủ hơn vì trông đơn giản hơn ...
eadmaster

Thất bại nếu $PATHchứ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 $PATHgiống /bin:/usr/bin:nhau)
Stéphane Chazelas 22/2/2015

@ StéphaneChazelas - Điểm hay - Tôi đã có cái đó ở cái cũ. Không những gì Ngoài ra ... nghĩa là gì? Bạn đang nói rằng một $PATHthành phần có thể chứa :? Dù sao; Tôi đã sửa ::điều.
mikeerv

IFS=:; $PATHtách /bin:/usr/bin:thành (/bin /usr/bin)các vỏ POSIX, không (/bin /usr/bin '')(ngoại trừ zshtrong mô phỏng sh, yash, pdksh và các phiên bản cũ hơn của mksh)
Stéphane Chazelas 22/215

Một $ PATH trống có nghĩa là tìm kiếm trong thư mục hiện tại, /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.
Stéphane Chazelas
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.