ฉันจะตรวจสอบว่าโปรแกรมนั้นมีอยู่จากสคริปต์ Bash ได้อย่างไร


2209

ฉันจะตรวจสอบว่าโปรแกรมมีอยู่ในวิธีที่จะส่งคืนข้อผิดพลาดและออกหรือดำเนินการต่อกับสคริปต์ได้อย่างไร

ดูเหมือนว่ามันควรจะง่าย แต่มันทำให้ฉันนิ่งงัน


"โปรแกรม" คืออะไร? มันรวมฟังก์ชั่นและชื่อแทนหรือไม่? whichผลตอบแทนที่แท้จริงสำหรับสิ่งเหล่านี้ typeโดยไม่มีอาร์กิวเมนต์จะส่งกลับค่าจริงสำหรับคำที่สงวนไว้และเชลล์บิลด์อิน หาก "โปรแกรม" หมายถึง "ไม่เปลี่ยนแปลงใน$PATH" ให้ดูคำตอบนี้
Tom Hale

คำตอบ:


3052

ตอบ

รองรับ POSIX:

command -v <the_command>

สำหรับสภาพแวดล้อมเฉพาะของ Bash:

hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords

คำอธิบาย

whichหลีกเลี่ยงการ ไม่เพียง แต่มันเป็นกระบวนการภายนอกที่คุณกำลังเปิดตัวสำหรับการทำน้อยมาก (builtins หมายถึงชอบhash, typeหรือcommandมีวิธีที่ถูกกว่า) นอกจากนี้คุณยังสามารถพึ่งพา builtins ที่จริงทำในสิ่งที่คุณต้องการในขณะที่ผลกระทบจากคำสั่งภายนอกอาจแตกต่างกันได้อย่างง่ายดายจาก ระบบต่อระบบ

ทำไมต้องแคร์

  • ระบบปฏิบัติการหลายแห่งมีwhichที่ไม่ได้ตั้งสถานะทางออกหมายถึงif which fooจะไม่ได้ทำงานที่นั่นและจะเสมอรายงานว่าfooมีอยู่ถึงแม้ว่ามันจะไม่ได้ (หมายเหตุว่าบางหอย POSIX ปรากฏว่าทำเช่นนี้สำหรับhashเกินไป)
  • ระบบปฏิบัติการหลายระบบทำwhichสิ่งที่กำหนดเองและสิ่งที่ชั่วร้ายเช่นเปลี่ยนผลลัพธ์หรือแม้แต่ขอตัวจัดการแพคเกจ

whichดังนั้นไม่ได้ใช้ ใช้หนึ่งในเหล่านี้แทน:

$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }
$ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; }

(ไมเนอร์ด้านหมายเหตุ: บางคนจะแนะนำ2>&-เป็นเหมือนกัน2>/dev/nullแต่สั้น - นี้เป็นความจริง . 2>&-ปิด FD 2 ซึ่งทำให้เกิดข้อผิดพลาดในโปรแกรมเมื่อพยายามที่จะเขียนถึง stderr ซึ่งแตกต่างกันมากประสบความสำเร็จจากการเขียนไปและทิ้งเอาท์พุท (และอันตราย!)

หากแฮชปังของคุณคือ/bin/shสิ่งที่คุณควรใส่ใจกับสิ่งที่ POSIX พูด typeและhashรหัสการออกของยังไม่ได้กำหนดไว้อย่างดีเยี่ยมโดย POSIX และhashจะเห็นว่าการออกสำเร็จเมื่อคำสั่งยังไม่มีอยู่ (ยังไม่เห็นสิ่งนี้ด้วยtype) commandสถานะการออกของ POSIX นั้นกำหนดไว้อย่างดีดังนั้นจึงน่าจะเป็นวิธีที่ปลอดภัยที่สุดที่จะใช้

หากใช้สคริปต์ของคุณbashแม้ว่ากฎ POSIX ไม่ได้เรื่องจริงๆอีกต่อไปและทั้งสองtypeและhashกลายเป็นที่ที่ดีที่สุดที่ปลอดภัยในการใช้งาน typeตอนนี้มีการ-Pค้นหาเพียงแค่PATHและhashมีผลข้างเคียงที่ตำแหน่งของคำสั่งจะถูกแฮช (สำหรับการค้นหาได้เร็วขึ้นในครั้งต่อไปที่คุณใช้) ซึ่งมักจะเป็นสิ่งที่ดีเพราะคุณอาจตรวจสอบการมีอยู่ของมันเพื่อใช้งานจริง .

ตัวอย่างง่ายๆนี่คือฟังก์ชั่นที่จะทำงานgdateหากมีอยู่มิฉะนั้นdate:

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "$@"
    else
        date "$@"
    fi
}

35
@Geert: ส่วน &> / dev / null ซ่อนข้อความ 'ประเภท' ส่งเสียงเมื่อไม่มี 'foo' &> 2 บนเสียงก้องทำให้แน่ใจว่าได้ส่งข้อความข้อผิดพลาดไปยังข้อผิดพลาดมาตรฐานแทนการส่งออกมาตรฐาน; เพราะนั่นคือการประชุม ทั้งคู่ปรากฏบนเทอร์มินัลของคุณ แต่ข้อผิดพลาดมาตรฐานคือเอาต์พุตที่ต้องการสำหรับข้อความแสดงข้อผิดพลาดและคำเตือนที่ไม่คาดคิด
lhunath

5
แฟ
ล็ก

130
สำหรับผู้ที่ไม่คุ้นเคยกับการเปลี่ยนเส้นทางแบบ 'ขั้นสูง' ในการทุบตี: 1) 2>&- ("ไฟล์คำอธิบายปิดเอาต์พุต 2" ซึ่งเป็น stderr)มีผลลัพธ์แบบเดียวกันกับ2> /dev/null; 2) >&2เป็นทางลัด1>&2ซึ่งคุณอาจจำได้ว่าเป็น "เปลี่ยนเส้นทาง stdout ไปยัง stderr" ดูหน้าการเปลี่ยนเส้นทางคำแนะนำการเขียนสคริปต์ Bash ขั้นสูงสำหรับข้อมูลเพิ่มเติม
mikewaters

9
@mikewaters ABS มีลักษณะค่อนข้างสูงและอธิบายถึงความหลากหลายของฟังก์ชั่นการทุบตีและการทุบตีที่ไม่ใช่การทุบตี CLI แต่ก็มีความประมาทในหลาย ๆ ด้านและไม่ปฏิบัติตามแนวปฏิบัติที่ดี ฉันมีพื้นที่ไม่เพียงพอในความคิดเห็นนี้เพื่อเขียนบทความ แต่ฉันสามารถวางตัวอย่างแบบสุ่มไม่กี่รหัส BAD: while read element ; do .. done <<< $(echo ${ArrayVar[*]}), for word in $(fgrep -l $ORIGINAL *.txt), ls -l "$directory" | sed 1d {{สำหรับในseq $BEGIN $END}} ... หลายคนได้พยายามที่จะติดต่อผู้เขียนและนำเสนอการปรับปรุง แต่ก็ไม่มีวิกิพีเดียและการร้องขอมีที่ดินบนหูหนวก
lhunath

56
@mikewaters 2>&-คือไม่ได้2>/dev/nullเช่นเดียวกับ /dev/nullอดีตปิดอธิบายไฟล์ในขณะที่หลังจะเปลี่ยนเส้นทางไปยัง คุณอาจไม่เห็นข้อผิดพลาดเนื่องจากโปรแกรมพยายามแจ้งให้คุณทราบถึง stderr ว่า stderr ถูกปิด
nyuszika7h

575

ต่อไปนี้เป็นวิธีพกพาเพื่อตรวจสอบว่ามีคำสั่งอยู่หรือไม่$PATH และสามารถทำงานได้:

[ -x "$(command -v foo)" ]

ตัวอย่าง:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi

$PATHการตรวจสอบการปฏิบัติการเป็นสิ่งจำเป็นเพราะทุบตีส่งกลับไฟล์ไม่สามารถทำงานได้หากไม่มีแฟ้มที่ปฏิบัติการด้วยชื่อที่พบใน

นอกจากนี้โปรดทราบว่าหากไฟล์ที่ไม่สามารถเรียกใช้งานได้ซึ่งมีชื่อเดียวกันกับไฟล์ที่เรียกทำงานได้มีอยู่ก่อนหน้านี้$PATHDash จะส่งคืนไฟล์เดิมแม้ว่าไฟล์ดังกล่าวจะถูกเรียกใช้งานก็ตาม นี่เป็นข้อผิดพลาดและละเมิดมาตรฐาน POSIX [ รายงานข้อผิดพลาด ] [ มาตรฐาน ]

นอกจากนี้สิ่งนี้จะล้มเหลวหากคำสั่งที่คุณต้องการถูกกำหนดเป็นนามแฝง


4
จะcommand -vสร้างเส้นทางแม้แต่ไฟล์ที่ไม่สามารถเรียกใช้งานได้หรือไม่ นั่นคือ -x จำเป็นจริงๆเหรอ?
einpoklum

5
@einpoklum -xทดสอบว่าไฟล์นั้นสามารถเรียกใช้งานได้ซึ่งเป็นปัญหาที่เกิดขึ้น
Ken Sharp

3
@KenSharp: แต่ดูเหมือนว่าจะซ้ำซ้อนเนื่องจากcommandตัวเองจะทดสอบสำหรับการปฏิบัติการ - จะไม่ได้หรือไม่
einpoklum

13
@einpoklum ใช่มันเป็นสิ่งที่จำเป็น ในความเป็นจริงแม้แต่วิธีนี้อาจแตกในกรณีขอบหนึ่ง ขอขอบคุณที่แจ้งเรื่องนี้ให้ฉันทราบ เส้นประทุบตีและ zsh ข้ามไปที่ไฟล์ที่ไม่สามารถเรียกใช้งานได้$PATHเมื่อเรียกใช้คำสั่ง อย่างไรก็ตามพฤติกรรมของcommand -vไม่สอดคล้องกันมาก ในเส้นประมันจะส่งคืนไฟล์แรกที่จับคู่เข้า$PATHมาโดยไม่คำนึงว่าสามารถเรียกใช้งานได้หรือไม่ ใน bash จะส่งคืนการจับคู่ที่เรียกใช้งานได้ครั้งแรก$PATHแต่ถ้าไม่มีจะสามารถส่งคืนไฟล์ที่ไม่สามารถเรียกทำงานได้ และใน zsh มันจะไม่ส่งคืนไฟล์ที่ไม่สามารถเรียกใช้งานได้
nyuszika7h

4
เท่าที่ฉันสามารถบอกได้ว่าdashมีเพียงหนึ่งในสามที่ไม่สอดคล้องกับ POSIX [ -x "$(command -v COMMANDNAME)"]จะทำงานในอีกสองคน ดูเหมือนว่าข้อผิดพลาดนี้ได้รับการรายงานแล้ว แต่ยังไม่ได้รับคำตอบใด ๆ : bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
nyuszika7h

210

ผมเห็นด้วยกับ lhunath ที่จะกีดกันการใช้whichและวิธีการแก้ปัญหาของเขาคือการที่ถูกต้องสมบูรณ์สำหรับผู้ใช้ทุบตี อย่างไรก็ตามเพื่อให้พกพาได้มากกว่าcommand -vให้ใช้แทน:

$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed.  Aborting." >&2; exit 1; }

คำสั่งcommandสอดคล้องกับ POSIX ดูที่นี่สำหรับข้อมูลจำเพาะ: คำสั่ง - รันคำสั่งแบบง่าย

บันทึก: typeเป็นไปตาม POSIX แต่type -Pไม่ใช่


3
เหมือนกับด้านบน - exit 1;ฆ่า xterm ถ้าถูกเรียกจากที่นั่น
ผู้ใช้ไม่ทราบ

1
สิ่งนี้จะใช้ไม่ได้กับ sh มาตรฐาน: คุณ &> ไม่ใช่คำแนะนำการเปลี่ยนเส้นทางที่ถูกต้อง
jyavenard

7
@jyavenard: คำถามคือแท็กทุบตี&>/dev/nullจึงเปลี่ยนเส้นทางสัญกรณ์ทุบตีเฉพาะรัดกุมมากขึ้น >/dev/null 2>&1แต่ผมเห็นด้วยกับคุณจริงๆสิ่งที่สำคัญก็คือการพกพาผมได้แก้ไขคำตอบของฉันดังนั้นตอนนี้ใช้การเปลี่ยนเส้นทางการดวลจุดโทษมาตรฐาน
GregV

เพื่อปรับปรุงคำตอบนี้ให้มากขึ้นฉันจะทำสองสิ่ง: 1: ใช้ "&>" เพื่อทำให้มันง่ายขึ้นเช่นคำตอบของ Josh 2: แยก {} เป็นบรรทัดพิเศษวางแท็บหน้า echo เพื่อให้อ่านได้
knocte

ฉันเพียงแค่ใส่นี้ซับในการทำงานทุบตีหากใครต้องการมัน ... github.com/equant/my_bash_tools/blob/master/tarp.bash
equant

94

ฉันมีฟังก์ชั่นที่กำหนดไว้ใน. bashrc ของฉันซึ่งทำให้ง่ายขึ้น

command_exists () {
    type "$1" &> /dev/null ;
}

นี่คือตัวอย่างการใช้งาน (จากของฉัน.bash_profile)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi

อะไร&>ทำอย่างไร
Saad Malik


&>อาจไม่สามารถใช้ได้ใน Bash เวอร์ชันของคุณ รหัสของ Marcello ควรทำงานได้ดี มันทำสิ่งเดียวกัน
Josh Strater

3
ล้มเหลวในตัวบิวด์อินและคำที่สงวนไว้: ลองใช้คำนี้thenเพื่อเป็นตัวอย่าง ดูคำตอบนี้$PATHหากคุณต้องการปฏิบัติการที่จะอยู่ใน
Tom Hale

84

ขึ้นอยู่กับว่าคุณต้องการทราบว่ามีอยู่ในหนึ่งในไดเรกทอรีใน$PATHตัวแปรหรือไม่หรือคุณทราบตำแหน่งที่แน่นอนของมัน หากคุณต้องการทราบว่าอยู่ใน$PATHตัวแปรหรือไม่ให้ใช้

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi

มิฉะนั้นใช้

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi

การเปลี่ยนเส้นทางไปยัง/dev/null/ในตัวอย่างแรกจะไม่แสดงผลลัพธ์ของwhichโปรแกรม


22
คุณไม่ควรใช้คำว่า "ซึ่ง" ด้วยเหตุผลที่ระบุไว้ในความคิดเห็นของฉัน
lhunath

39

การขยายคำตอบของ @ lhunath และ @ GregV ต่อไปนี้เป็นรหัสสำหรับผู้ที่ต้องการนำการตรวจสอบนั้นออกมาอย่างง่ายดายในifคำสั่ง:

exists()
{
  command -v "$1" >/dev/null 2>&1
}

นี่คือวิธีการใช้งาน:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi

13
ความเต็มใจที่จะเรียนรู้และปรับปรุงต้องได้รับรางวัล +1 นี่สะอาดและเรียบง่าย สิ่งเดียวที่ฉันสามารถเพิ่มได้ก็คือการcommandประสบความสำเร็จแม้สำหรับนามแฝงซึ่งอาจจะค่อนข้างใช้งานง่าย การตรวจสอบว่ามีอยู่ในเชลล์แบบโต้ตอบจะให้ผลลัพธ์ที่แตกต่างจากเมื่อคุณย้ายมันไปที่สคริปต์
Palec

1
ฉันเพิ่งผ่านการทดสอบและการใช้shopt -u expand_aliasesละเว้น / กลองนามแฝง (เหมือนalias ls='ls -F'ที่กล่าวไว้ในคำตอบอื่น) และแก้ไขพวกเขาผ่านทางshopt -s expand_aliases command -vดังนั้นจึงควรตั้งค่าก่อนการตรวจสอบและยกเลิกการตั้งค่าแม้ว่ามันอาจส่งผลกระทบต่อค่าส่งคืนฟังก์ชั่นถ้าคุณไม่ได้จับและส่งกลับผลลัพธ์ของการเรียกคำสั่งอย่างชัดเจน
dragon788

24

ลองใช้:

test -x filename

หรือ

[ -x filename ]

จาก Bash manpage ภายใต้การแสดงออกแบบมีเงื่อนไข :

 -x file
          True if file exists and is executable.

26
นั่นหมายความว่าคุณต้องทราบเส้นทางแบบเต็มของแอปพลิเคชันแล้ว
lhunath

12
OP ไม่ได้ระบุว่าเขาต้องการตรวจสอบอินสแตนซ์ที่เฉพาะเจาะจงหรืออินสแตนซ์ที่สามารถเรียกใช้งานได้ ... ฉันตอบตามที่ฉันอ่าน
dmckee --- ผู้ดูแลอดีตลูกแมว

16

หากต้องการใช้hash, เป็น @lhunath แสดงให้เห็นในสคริปต์ทุบตี:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi

สคริปต์นี้วิ่งhashและการตรวจสอบแล้วถ้ารหัสทางออกของคำสั่งล่าสุดค่าที่เก็บไว้ในเท่ากับ$? 1หากhashไม่พบรหัสทางออกจะเป็นfoo 1ถ้าเป็นปัจจุบันรหัสทางออกจะเป็นfoo0

&> /dev/nullเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานและเอาต์พุตมาตรฐานจากhashเพื่อไม่ให้ปรากฏบนหน้าจอและecho >&2เขียนข้อความไปยังข้อผิดพลาดมาตรฐาน


8
ทำไมไม่เป็นเช่นนั้นif hash foo &> /dev/null; then ...?
Beni Cherniavsky-Paskin

9

ฉันไม่เคยได้รับคำตอบก่อนหน้านี้มาทำงานในกล่องที่ฉันสามารถเข้าถึงได้ สำหรับหนึ่งtypeได้รับการติดตั้ง (ทำในสิ่งที่ทำmore) ดังนั้นจึงจำเป็นต้องมีคำสั่งในตัว คำสั่งนี้ใช้ได้สำหรับฉัน:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi

3
วงเล็บไม่ได้เป็นส่วนหนึ่งของไวยากรณ์เพียงแค่ใช้if if builtin type -p vim; then ...และ backticks นั้นมีความเก่าแก่และซินแท็คซ์ที่$()ได้รับการสนับสนุนแม้กระทั่งshในระบบที่ทันสมัยทั้งหมด
nyuszika7h

9

ตรวจสอบการอ้างอิงหลายรายการและแจ้งสถานะแก่ผู้ใช้ปลายทาง

for cmd in latex pandoc; do
  printf '%-10s' "$cmd"
  if hash "$cmd" 2>/dev/null; then
    echo OK
  else
    echo missing
  fi
done

ตัวอย่างผลลัพธ์:

latex     OK
pandoc    missing

ปรับ10ความยาวสูงสุดของคำสั่ง มันไม่อัตโนมัติเพราะฉันไม่เห็นวิธี POSIX แบบไม่ต้องทำ: ฉันจะจัดแนวคอลัมน์ของตารางที่คั่นด้วยช่องว่างใน Bash ได้อย่างไร

ตรวจสอบว่าaptมีการติดตั้งแพคเกจบางอย่างdpkg -sและติดตั้งหรือไม่

ดู: ตรวจสอบว่ามีการติดตั้งแพกเกจ apt-get หรือไม่จากนั้นติดตั้งหากไม่ได้อยู่ใน Linux

ก่อนหน้านี้ถูกกล่าวถึงที่: ฉันจะตรวจสอบว่าโปรแกรมนั้นมีอยู่จากสคริปต์ Bash ได้อย่างไร


1
วิธีที่ไม่ต้องทำอย่างละเอียด: 1) กำจัดตัวระบุความกว้าง; 2) เพิ่มช่องว่างหลังจาก printf ชื่อคำสั่งของคุณ; 3) แปะวนลูปไปที่column -t(ส่วนหนึ่งของ util-linux)
Patrice Levesque

8

หากคุณตรวจสอบการมีอยู่ของโปรแกรมคุณอาจจะเรียกใช้ในภายหลัง ทำไมไม่ลองใช้มันตั้งแต่แรกล่ะ?

if foo --version >/dev/null 2>&1; then
    echo Found
else
    echo Not found
fi

เป็นการตรวจสอบที่น่าเชื่อถือมากขึ้นว่าโปรแกรมทำงานได้ดีกว่าเพียงแค่ดูที่ไดเรกทอรีเส้นทางและการอนุญาตไฟล์

ยิ่งไปกว่านั้นคุณจะได้รับผลลัพธ์ที่เป็นประโยชน์จากโปรแกรมของคุณเช่นเวอร์ชันของมัน

แน่นอนว่าข้อเสียคือบางโปรแกรมอาจเริ่มทำงานหนักและบางโปรแกรมไม่มี--versionตัวเลือกในการออกจาก (ทันทีและสำเร็จ)


6

hash foo 2>/dev/null: ทำงานร่วมกับ Z shell (Zsh), Bash, Dashและashเถ้า

type -p foo: ดูเหมือนว่าจะทำงานกับ Z shell, Bash and ash ( BusyBox ) แต่ไม่ใช่ Dash (แปลว่ามัน-pเป็นอาร์กิวเมนต์)

command -v foo: ทำงานร่วมกับ Z shell, Bash, Dash, แต่ไม่ใช่ Ash (BusyBox) (-ash: command: not found )

โปรดทราบว่าbuiltinไม่สามารถใช้ได้กับเถ้าและเส้นประ


4

ใช้ Bash builtins หากคุณสามารถ:

which programname

...

type -P programname

15
ฮะ? whichไม่ใช่ Bash builtin
tripleee

พิมพ์ -P จะต้องเลือกชื่อโปรแกรมดูคำตอบที่ยอมรับ
RobertG

@RobertG สิ่งที่ฉันเห็น-Pคือไม่ใช่ POSIX ทำไมถึงtype -Pเลือก?
mikemaccana

ฉันควรจะใช้ถ้อยคำว่า "เป็นที่ต้องการในสภาพแวดล้อมทุบตี" - ตามที่ฉันตั้งใจจะตอบความคิดเห็นก่อนหน้านี้เฉพาะสำหรับทุบตี ยังไงก็ตามนั่นคือเมื่อหลายปีก่อน - ฉันเดาว่าฉันควรจะชี้ให้คุณเห็นคำตอบที่ระบุว่าเป็น "accpeted" อีกครั้ง
RobertG

4

คำสั่ง-vทำงานได้ดีหากตั้งค่าตัวเลือก POSIX_BUILTINS ไว้สำหรับ<command>ทดสอบ แต่จะล้มเหลวได้หากไม่ได้ (มันใช้งานได้สำหรับฉันมาหลายปีแล้ว แต่ฉันเพิ่งพบเจอสิ่งที่ไม่ได้ผล)

ฉันพบว่าสิ่งต่อไปนี้จะล้มเหลวได้มากกว่านี้:

test -x $(which <command>)

เนื่องจากเป็นการทดสอบสามสิ่ง: เส้นทางการมีอยู่และการอนุญาตให้ดำเนินการ


ใช้งานไม่ได้ test -x $(which ls)ผลตอบแทน 0 เช่นเดียวtest -x $(which sudo)แม้ว่าจะlsมีการติดตั้งและทำงานได้และsudoไม่ได้ติดตั้งแม้จะอยู่ในภาชนะนักเทียบท่าที่ผมกำลังทำงานใน.
สาหร่าย

@algal คุณต้องใช้เครื่องหมายคำพูดที่ฉันคิดว่างั้นtest -x "$(which <command>)"
JoniVR

@algal อาจจะlsเป็นนามแฝงหรือไม่ ฉันไม่คิดว่ามันจะทำงานถ้าคำสั่งมีพารามิเตอร์
AnthonyC

3

สำหรับผู้ที่สนใจไม่มีวิธีการในคำตอบก่อนหน้านี้ทำงานถ้าคุณต้องการตรวจสอบห้องสมุดที่ติดตั้ง ฉันคิดว่าคุณถูกทิ้งอย่างใดอย่างหนึ่งด้วยการตรวจสอบเส้นทาง (อาจเป็นไฟล์ส่วนหัวและอื่น ๆ ) หรือสิ่งนี้ (ถ้าคุณใช้การกระจายแบบ Debian):

dpkg --status libdb-dev | grep -q not-installed

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi

ดังที่คุณเห็นจากด้านบนคำตอบ "0" จากแบบสอบถามหมายความว่าไม่ได้ติดตั้งแพ็คเกจ นี่คือฟังก์ชั่นของ "grep" - "0" หมายถึงพบการแข่งขัน "1" หมายถึงไม่พบการแข่งขัน


10
อย่างไรก็ตามรูปแบบการต่อต้านcmd; if [ $? -eq 0 ]; thenควรได้รับการ refactored ถึงif cmd; then
tripleee

ใช้งานได้กับ libs ที่ติดตั้งผ่านdpkgหรือapt
Weijun Zhou

3

มีตัวเลือกมากมายที่นี่ แต่ฉันรู้สึกประหลาดใจเลยว่าไม่มีซับในอย่างรวดเร็ว นี่คือสิ่งที่ฉันใช้ในตอนต้นของสคริปต์ของฉัน:

[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

นี่คือคำตอบที่เลือกที่นี่และแหล่งอื่น


2

ฉันจะบอกว่าไม่มีวิธีการพกพาและเชื่อถือได้ 100% เนื่องจากการห้อยaliasES ตัวอย่างเช่น:

alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/

แน่นอนมีเพียงอันสุดท้ายเท่านั้นที่เป็นปัญหา (ไม่มีความผิดต่อริงโก้!) แต่ทั้งหมดของพวกเขาที่ถูกต้องaliasES command -vจากมุมมองของ

เพื่อที่จะปฏิเสธคนที่ชอบห้อยringoเราต้องแยกเอาท์พุทของaliasคำสั่งในตัวของเชลล์และเรียกคืนเข้าไป ( command -vไม่ดีกว่าaliasที่นี่) ไม่มีวิธีแก้ปัญหาแบบพกพาสำหรับมันและแม้แต่ Bash- วิธีแก้ปัญหาเฉพาะค่อนข้างน่าเบื่อ

โปรดทราบว่าวิธีแก้ปัญหาเช่นนี้จะปฏิเสธโดยไม่มีเงื่อนไขalias ls='ls -F':

test() { command -v $1 | grep -qv alias }

จุดดี. อย่างไรก็ตามเมื่อเรียกใช้จากภายในสคริปต์ทุบตีนามแฝงจะไม่สามารถมองเห็นได้
Basil Musa

1
นอกจากนี้ยังมีปัญหามันจะกลับเท็จเมื่อตรวจสอบคำสั่ง 'นามแฝง' เมื่อมันควรกลับมาจริง ตัวอย่าง: ทดสอบ "นามแฝง"
Basil Musa

2
ฉันเพิ่งผ่านการทดสอบและการใช้shopt -u expand_aliasesละเว้น / ซ่อนชื่อแทนเหล่านี้และแสดงให้เห็นว่าพวกเขาผ่านทางshopt -s expand_aliases command -v
dragon788

2

สิ่งนี้จะบอกตามตำแหน่งหากมีโปรแกรมอยู่หรือไม่:

    if [ -x /usr/bin/yum ]; then
        echo "This is Centos"
    fi

ใช่ฉันเพิ่มคำสั่งนี้ถ้าคุณต้องการติดตั้งแพคเกจใน sevrer, Open suse, centos, Debian
Klevin Kona

การเน้นไวยากรณ์ถูกปิดในบรรทัด "echo" ทางออกคืออะไร? มันแนะนำสคริปต์ Bash ที่ควรจะแตกต่างกันหรือไม่?
Peter Mortensen

@PeterMortensen การเน้นไวยากรณ์ปิดอยู่เนื่องจากไม่รู้จักว่าเป็นสตริง
Adrien

1

whichคำสั่งอาจจะมีประโยชน์คนที่

มันจะคืนค่า 0 หากพบไฟล์ที่เรียกทำงานได้และส่งคืน 1 หากไม่พบไฟล์ปฏิบัติการหรือไม่:

NAME

       which - locate a command

SYNOPSIS

       which [-a] filename ...

DESCRIPTION

       which returns the pathnames of the files which would
       be executed in the current environment, had its
       arguments been given as commands in a strictly
       POSIX-conformant shell. It does this by searching
       the PATH for executable files matching the names
       of the arguments.

OPTIONS

       -a     print all matching pathnames of each argument

EXIT STATUS

       0      if all specified commands are 
              found and executable

       1      if one or more specified commands is nonexistent
              or not executable

       2      if an invalid option is specified

สิ่งที่ดีเกี่ยวกับการwhichที่จะระบุว่าปฏิบัติการที่มีอยู่ในสภาพแวดล้อมที่whichทำงานใน - มันช่วยประหยัดปัญหาบางอย่าง ...


ใช้ตัวเลือกนี้หากคุณกำลังมองหาไฟล์เรียกทำงานที่มีชื่อว่า foo แต่ดูคำตอบของฉันหากคุณต้องการตรวจสอบไฟล์ / พา ธ / ไปยัง / a / named / foo เฉพาะ นอกจากนี้ทราบว่าซึ่งอาจไม่สามารถใช้ได้ในระบบน้อยที่สุดบางส่วน แต่มันควรจะนำเสนอในการติดตั้งใด ๆ เปี่ยมเต็ม ...
dmckee --- อดีตผู้ดูแลลูกแมว

9
อย่าพึ่งพาสถานะทางออกที่ ระบบปฏิบัติการหลายระบบมีระบบที่ไม่ได้กำหนดสถานะการออกนอกจาก 0
lhunath

1

การตั้งค่าของฉันสำหรับDebianเซิร์ฟเวอร์ :

ฉันมีปัญหาเมื่อมีหลายแพ็คเกจที่ใช้ชื่อเดียวกัน

apache2เช่น ดังนั้นนี่คือทางออกของฉัน:

function _apt_install() {
    apt-get install -y $1 > /dev/null
}

function _apt_install_norecommends() {
    apt-get install -y --no-install-recommends $1 > /dev/null
}
function _apt_available() {
    if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then
        echo "Package is available : $1"
        PACKAGE_INSTALL="1"
    else
        echo "Package $1 is NOT available for install"
        echo  "We can not continue without this package..."
        echo  "Exitting now.."
        exit 0
    fi
}
function _package_install {
    _apt_available $1
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: $1"
        else
            echo  "installing package : $1, please wait.."
            _apt_install $1
            sleep 0.5
        fi
    fi
}

function _package_install_no_recommends {
    _apt_available $1
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: $1"
        else
            echo  "installing package : $1, please wait.."
            _apt_install_norecommends $1
            sleep 0.5
        fi
    fi
}

1

หากคุณ / Gals ไม่สามารถหาคำตอบได้ที่นี่เพื่อทำงานและดึงผมออกจากหลังให้ลองเรียกใช้คำสั่งเดียวกันโดยใช้ bash -cไม่สามารถได้รับสิ่งที่อยู่ในคำตอบที่นี่เพื่อทำงานและมีการดึงผมออกมาจากด้านหลังของคุณพยายามที่จะเรียกใช้คำสั่งเดียวกันโดยใช้ เพียงแค่ดูเพ้อเพ้อนามนี้ นี่คือสิ่งที่เกิดขึ้นจริงเมื่อคุณเรียกใช้ $ (คำสั่งย่อย):

เป็นครั้งแรก มันสามารถให้ผลลัพธ์ที่แตกต่างอย่างสิ้นเชิง

$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls

ที่สอง มันสามารถให้ผลลัพธ์ที่ไม่มีเลย

$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found

ความแตกต่างเกิดจากความแตกต่างระหว่างโหมดการโต้ตอบและโหมดไม่โต้ตอบของเชลล์ ~ / .bashrc ของคุณจะอ่านได้ก็ต่อเมื่อเชลล์ไม่ใช่แบบล็อกอินและแบบโต้ตอบ อันที่สองดูแปลก ๆ เพราะต้องเกิดจากความแตกต่างของตัวแปรสภาพแวดล้อม PATH แต่ subshells สืบทอดสภาพแวดล้อม
Palec

ในกรณีของฉัน.bashrcมีการต่อเติม[ -z "$PS1" ] && returnโดย# If not running interactively, don't do anythingฉันเดาว่าเป็นเหตุผลที่แม้แต่การจัดหา bashrc อย่างชัดเจนในโหมดที่ไม่โต้ตอบก็ไม่ได้ช่วยอะไร ปัญหาสามารถแก้ไขได้ด้วยการเรียกสคริปต์ด้วยตัวดำเนินการ ss64.com/bash/source.html dot . ./script.shแต่นั่นไม่ใช่สิ่งที่เราต้องการจดจำให้พิมพ์ทุกครั้ง
user619271

1
การจัดหาสคริปต์ที่ไม่ควรมีแหล่งที่มาเป็นความคิดที่ไม่ดี ทั้งหมดที่ฉันพยายามจะพูดคือคำตอบของคุณมีน้อยเกี่ยวกับคำถามที่ถูกถามและเกี่ยวข้องกับ Bash และโหมดการโต้ตอบ (ไม่ใช่)
Palec

หากมันอธิบายสิ่งที่เกิดขึ้นในกรณีเหล่านี้มันจะเป็นภาคผนวกที่เป็นประโยชน์สำหรับคำตอบ
Palec

0

hash-variant มีหนึ่งหลุมพราง: บนบรรทัดคำสั่งคุณสามารถพิมพ์ตัวอย่างได้

one_folder/process

เพื่อให้กระบวนการทำงาน สำหรับเรื่องนี้โฟลเดอร์แม่ของ one_folder จะต้องอยู่ใน$ PATH แต่เมื่อคุณพยายามแฮชคำสั่งนี้มันจะประสบความสำเร็จเสมอ:

hash one_folder/process; echo $? # will always output '0'

4
"สำหรับกรณีนี้โฟลเดอร์หลักของ one_folder จะต้องอยู่ใน$PATH" - นี่ไม่ถูกต้องสมบูรณ์ ลองมัน. สำหรับการทำงาน, one_folder จะต้องอยู่ในไดเรกทอรีปัจจุบัน
สัญลักษณ์แทน

0

ฉันสองใช้ "command -v" เช่นนี้

md=$(command -v mkdirhier) ; alias md=${md:=mkdir}  # bash

emacs="$(command -v emacs) -nw" || emacs=nano
alias e=$emacs
[[ -z $(command -v jed) ]] && alias jed=$emacs

0

ฉันต้องตรวจสอบว่าติดตั้ง Git เป็นส่วนหนึ่งของการปรับใช้เซิร์ฟเวอร์CIของเราหรือไม่ สคริปต์ Bash สุดท้ายของฉันเป็นดังนี้ (เซิร์ฟเวอร์ Ubuntu):

if ! builtin type -p git &>/dev/null; then
  sudo apt-get -y install git-core
fi

3
เงื่อนไขค่อนข้างไร้ประโยชน์ modulo เวลาเริ่มต้นในการรัน apt-get เนื่องจาก apt-get จะเป็นที่พอใจและออกจากถ้า git-core ติดตั้งแล้ว
tripleee

3
เวลาเริ่มต้นของมันไม่สำคัญ แต่แรงจูงใจที่สำคัญกว่านั้นคือsudo: หากไม่มีเงื่อนไขก็จะหยุดและขอรหัสผ่านเสมอ (เว้นแต่คุณจะทำ sudo เมื่อเร็ว ๆ นี้) BTW มันอาจมีประโยชน์ในการทำเช่นsudo -p "Type your password to install missing git-core: "นั้นการแจ้งเตือนจะไม่ออกมาจากสีน้ำเงิน
Beni Cherniavsky-Paskin

0

ในการเลียนแบบ Bash type -P cmdเราสามารถใช้ POSIX env -i type cmd 1>/dev/null 2>&1ได้

man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.

ls() { echo 'Hello, world!'; }

ls
type ls
env -i type ls

cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }

7
ทำไมถึงถูก upvoted นี้ ระบบนี้ใช้งานได้จริงสำหรับคุณ? typeดูเหมือนว่าจะเป็นbuiltinเชลล์ส่วนใหญ่ดังนั้นจึงไม่สามารถทำงานได้เนื่องจากenvใช้execvpเพื่อรันcommandจึงcommandไม่สามารถเป็นได้builtin(และbuiltinจะทำงานในสภาพแวดล้อมเดียวกันเสมอ) นี้ล้มเหลวสำหรับฉันในbash, ksh93, zsh, busybox [a]shและdashทุกที่ให้typeเป็น builtin เปลือก
Adrian Frühwirth

0

หากไม่มีtypeคำสั่งภายนอกใด ๆ(ตามที่ให้ไว้ที่นี่ ) เราสามารถใช้ POSIX ได้env -i sh -c 'type cmd 1>/dev/null 2>&1':

# Portable version of Bash's type -P cmd (without output on stdout)
typep() {
   command -p env -i PATH="$PATH" sh -c '
      export LC_ALL=C LANG=C
      cmd="$1"
      cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"
      [ $? != 0 ] && exit 1
      case "$cmd" in
        *\ /*) exit 0;;
            *) printf "%s\n" "error: $cmd" 1>&2; exit 1;;
      esac
   ' _ "$1" || exit 1
}

# Get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp

อย่างน้อยในMac OS X v10.6.8 (Snow Leopard) โดยใช้ทุบตี 4.2.24 (2) ไม่ตรงกับย้ายcommand -v ls/bin/ls-temp


0

ในกรณีที่คุณต้องการตรวจสอบว่าโปรแกรมที่มีอยู่และเป็นจริงโปรแกรมไม่ทุบตีในตัวคำสั่งแล้วcommand, typeและhashไม่เหมาะสมสำหรับการทดสอบตามที่พวกเขากลับมา 0 สถานะออกทั้งหมดในตัวคำสั่ง

ตัวอย่างเช่นมีโปรแกรมเวลาที่มีคุณสมบัติมากกว่าคำสั่งในตัวเวลา เพื่อตรวจสอบว่ามีโปรแกรมอยู่หรือไม่ฉันขอแนะนำให้ใช้whichในตัวอย่างต่อไปนี้:

# First check if the time program exists
timeProg=`which time`
if [ "$timeProg" = "" ]
then
  echo "The time program does not exist on this system."
  exit 1
fi

# Invoke the time program
$timeProg --quiet -o result.txt -f "%S %U + p" du -sk ~
echo "Total CPU time: `dc -f result.txt` seconds"
rm result.txt

0

ฉันต้องการตอบคำถามเดียวกัน แต่ทำงานใน Makefile

install:
    @if [[ ! -x "$(shell command -v ghead)" ]]; then \
        echo 'ghead does not exist. Please install it.'; \
        exit -1; \
    fi

-1

ต้นฉบับ

#!/bin/bash

# Commands found in the hash table are checked for existence before being
# executed and non-existence forces a normal PATH search.
shopt -s checkhash

function exists() {
 local mycomm=$1; shift || return 1

 hash $mycomm 2>/dev/null || \
 printf "\xe2\x9c\x98 [ABRT]: $mycomm: command does not exist\n"; return 1;
}
readonly -f exists

exists notacmd
exists bash
hash
bash -c 'printf "Fin.\n"'

ผลลัพธ์

 [ABRT]: notacmd: command does not exist
hits    command
   0    /usr/bin/bash
Fin.

-1

ฉันใช้สิ่งนี้เพราะมันง่ายมาก:

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi

หรือ

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then
echo exists
else echo "not exists"
fi

มันใช้เชลล์บิวอินและสถานะก้องของโปรแกรมไปยังเอาต์พุตมาตรฐานและไม่มีข้อผิดพลาดมาตรฐาน ในทางกลับกันถ้าไม่พบคำสั่งสถานะ echos จะเป็นข้อผิดพลาดมาตรฐานเท่านั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.