วิธีการตั้งค่าไดเรกทอรีการทำงานปัจจุบันไปยังไดเรกทอรีของสคริปต์หรือไม่


569

ฉันกำลังเขียนสคริปต์ทุบตี ฉันต้องการไดเรกทอรีการทำงานปัจจุบันเป็นไดเรกทอรีที่สคริปต์ตั้งอยู่เสมอ

พฤติกรรมเริ่มต้นคือไดเรกทอรีการทำงานปัจจุบันในสคริปต์เป็นของเชลล์ที่ฉันเรียกใช้ แต่ฉันไม่ต้องการพฤติกรรมนี้


คุณได้พิจารณาใส่สคริปต์ wrapper ไว้ที่ / usr / bin ไปยัง cd ในไดเรกทอรีที่เหมาะสม (hardcoded) แล้วรันสคริปต์ของคุณหรือไม่
Dagg Nabbit

2
เหตุใดคุณจึงต้องการไดเรกทอรีของสคริปต์ อาจเป็นวิธีที่ดีกว่าในการแก้ปัญหาพื้นฐาน
bstpierre

12
ฉันแค่อยากจะชี้ให้เห็นว่าพฤติกรรมที่คุณเรียกว่า "ไม่พึงประสงค์อย่างเห็นได้ชัด" ในความเป็นจริงมีความจำเป็นอย่างยิ่ง - ถ้าฉันเรียกใช้myscript path/to/fileฉันคาดหวังว่าสคริปต์จะประเมินเส้นทาง / ถึง / ไฟล์เทียบกับไดเรกทอรีปัจจุบันของฉัน ที่จะอยู่ในนอกจากนี้คุณจะเกิดอะไรขึ้นสำหรับสคริปต์ที่ทำงานด้วยssh remotehost bash < ./myscriptในฐานะที่เป็นคำถามที่พบบ่อย BASH กล่าวถึง?
Gordon Davisson


3
cd "${BASH_SOURCE%/*}" || exit
กา

คำตอบ:


655
#!/bin/bash
cd "$(dirname "$0")"

7
dirname ส่งคืน '.' เมื่อใช้ bash ใน Windows ดังนั้นคำตอบของพอลก็ดีกว่า
Tvaroh

7
ส่งคืน '.' ด้วยเช่นกัน ใน Mac OSX
Ben Clayton

4
มันเป็นมูลค่า noting $0ว่าสิ่งที่สามารถทำลายถ้าการเชื่อมโยงสัญลักษณ์ทำให้ขึ้นเป็นส่วนหนึ่งของ ในสคริปต์ของคุณคุณอาจคาดหวังเช่นใน../../การอ้างถึงไดเรกทอรีสองระดับเหนือตำแหน่งของสคริปต์ แต่ไม่จำเป็นต้องเป็นกรณีนี้หากลิงก์สัญลักษณ์กำลังเล่นอยู่
Brian Gordon

20
ถ้าคุณเรียกว่าสคริปต์ที่เป็น./script, .เป็นไดเรกทอรีที่ถูกต้องและเปลี่ยนไป.มันก็จะจบลงในสมุดมากที่scriptตั้งอยู่เช่นในไดเรกทอรีการทำงานปัจจุบัน
ndim

6
ถ้าคุณเรียกใช้สคริปต์จากไดเรกทอรีปัจจุบันเช่นดังนั้นbash script.shแล้วค่าของการมี$0 script.shวิธีเดียวที่cdคำสั่งจะ "ทำงาน" สำหรับคุณก็คือเพราะคุณไม่สนใจคำสั่งที่ล้มเหลว หากคุณต้องใช้set -o errexit(aka:) set -eเพื่อให้แน่ใจว่าสคริปต์ของคุณไม่ได้ผ่านคำสั่งที่ล้มเหลวในอดีตสิ่งนี้จะไม่ทำงานเพราะcd script.shเป็นข้อผิดพลาด วิธีการ [bash เฉพาะ] ที่เชื่อถือได้คือcd "$(dirname ${BASH_SOURCE[0]})"
Bruno Bronosky

322

ต่อไปนี้ยังใช้งานได้:

cd "${0%/*}"

ไวยากรณ์อธิบายไว้อย่างละเอียดในนี้คำตอบ StackOverflow


17
คำอธิบายวิธีการทำงาน: stackoverflow.com/questions/6393551/…
kenorb

25
อย่าลืมใส่เครื่องหมายคำพูดหากเส้นทางนั้นมีช่องว่าง ie cd "$ {0% / *}"
H.Rabiee

17
สิ่งนี้จะล้มเหลวหากสคริปต์ถูกเรียกด้วยbash script.sh- $0จะเป็นชื่อของไฟล์เท่านั้น
Chris Watts

17
ฉันจะบอกว่าคำตอบนี้สั้นเกินไป คุณแทบจะไม่ได้เรียนรู้อะไรเกี่ยวกับไวยากรณ์ คำอธิบายบางอย่างเกี่ยวกับวิธีการอ่านนี้จะเป็นประโยชน์
fraxture

2
เคล็ดลับนี้ไม่ได้วางไข่ subshell เช่นกันดังนั้นจึงเป็นสิ่งที่ดีสำหรับความเร็ว freaks
teknopaul

184

ลองใช้ liners แบบง่าย ๆ ดังต่อไปนี้:


สำหรับ UNIX / OSX / Linux ทั้งหมด

dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P)

ทุบตี

dir=$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)

หมายเหตุ: ใช้เครื่องหมายขีดกลางสองตัว (-) ในคำสั่งเพื่อบ่งบอกถึงตัวเลือกการสิ้นสุดคำสั่งดังนั้นไฟล์ที่มีเครื่องหมายขีดคั่นหรืออักขระพิเศษอื่น ๆ จะไม่ทำให้คำสั่งหยุดทำงาน

หมายเหตุ: ในทุบตีใช้${BASH_SOURCE[0]}ในความโปรดปรานของ$0มิฉะนั้นเส้นทางที่สามารถทำลายเมื่อจัดหามัน ( source/ .)


สำหรับ Linux, Mac และ * BSD อื่น ๆ :

cd "$(dirname "$(realpath "$0")")";

หมายเหตุ: realpathควรติดตั้งในการกระจาย Linux ที่เป็นที่นิยมมากที่สุดโดยค่าเริ่มต้น (เช่น Ubuntu) แต่ในบางตัวอาจหายไปดังนั้นคุณต้องติดตั้ง

หมายเหตุ: หากคุณกำลังใช้ทุบตีใช้${BASH_SOURCE[0]}ในความโปรดปรานของ$0มิฉะนั้นเส้นทางที่สามารถทำลายเมื่อจัดหามัน ( source/ .)

มิฉะนั้นคุณสามารถลองสิ่งนั้น (จะใช้เครื่องมือที่มีอยู่ก่อน):

cd "$(dirname "$(readlink -f "$0" || realpath "$0")")"

สำหรับ Linux โดยเฉพาะ:

cd "$(dirname "$(readlink -f "$0")")"

ใช้ GNU readlink บน * BSD / Mac:

cd "$(dirname "$(greadlink -f "$0")")"

หมายเหตุ: คุณต้องcoreutilsติดตั้ง (เช่น 1. ติดตั้งHomebrew , 2. brew install coreutils)


ในทุบตี

ใน bash คุณสามารถใช้การขยายพารามิเตอร์เพื่อให้บรรลุเช่น:

cd "${0%/*}"

แต่จะไม่ทำงานหากสคริปต์ถูกเรียกใช้จากไดเรกทอรีเดียวกัน

หรือคุณสามารถกำหนดฟังก์ชั่นต่อไปนี้ใน bash:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

ฟังก์ชั่นนี้ใช้เวลา 1 ข้อโต้แย้ง หากอาร์กิวเมนต์มีเส้นทางที่แน่นอนอยู่แล้วให้พิมพ์ตามที่เป็นอยู่มิฉะนั้นพิมพ์$PWDตัวแปร + อาร์กิวเมนต์ชื่อไฟล์ (โดยไม่มี./คำนำหน้า)

หรือนี่คือรุ่นที่นำมาจาก.bashrcไฟล์Debian :

function realpath()
{
    f=$@
    if [ -d "$f" ]; then
        base=""
        dir="$f"
    else
        base="/$(basename "$f")"
        dir=$(dirname "$f")
    fi
    dir=$(cd "$dir" && /bin/pwd)
    echo "$dir$base"
}

ที่เกี่ยวข้อง:

ดูสิ่งนี้ด้วย:

ฉันจะดูพฤติกรรมของ readlink ของ GNU บน Mac ได้อย่างไร


4
คำตอบที่ดีกว่าคำตอบที่ได้รับความนิยมมากเพราะมันแก้ไข syslinks และบัญชีสำหรับ OS ที่แตกต่าง ขอบคุณ!
เดนนิส

ขอบคุณสำหรับคำตอบนี้ ส่วนบนทำงานบน Mac ของฉัน ... แต่สวิตช์ - ทำอะไรในcpคำสั่ง @kenorb?
TobyG

3
เครื่องหมายขีดกลางคู่ ( --) ใช้ในคำสั่งเพื่อบ่งบอกถึงตัวเลือกคำสั่งสุดท้ายดังนั้นไฟล์ที่มีเครื่องหมายขีดคั่นหรืออักขระพิเศษอื่น ๆ จะไม่ทำให้คำสั่งหยุดทำงาน ลองเช่นสร้างไฟล์ผ่านtouch "-test"และtouch -- -testจากนั้นลบไฟล์ผ่านrm "-test"และrm -- -testและดูความแตกต่าง
kenorb

1
ฉันจะลบ upvote ของฉันถ้าทำได้: realpath is deprecated unix.stackexchange.com/questions/136494/…
lsh

1
@lsh มันบอกว่ามีเพียงrealpathแพ็คเกจDebian เท่านั้นที่เลิกใช้แล้วไม่ใช่ GNUrealpathGNU หากคุณคิดว่ายังไม่ชัดเจนคุณสามารถแนะนำการแก้ไข
kenorb

73
cd "$(dirname "${BASH_SOURCE[0]}")"

มันเป็นเรื่องง่าย. มันได้ผล.


1
นี่ควรเป็นคำตอบที่ยอมรับได้ ทำงานบน OSX และ Linux Ubuntu
David Rissato Cruz

5
วิธีนี้ใช้งานได้ดีเมื่อสคริปต์ B มาจากสคริปต์อื่น A และคุณต้องใช้เส้นทางที่สัมพันธ์กับสคริปต์ B ขอบคุณ
Enrico

5
สิ่งนี้ยังใช้งานได้ในCygwinสำหรับพวกเราที่โชคร้ายพอที่จะต้องใช้ Windows
Bruno Bronosky

3
หรือcd "${BASH_SOURCE%/*}" || exit
CAW

ใช้งานได้กับ macOS Mojave
Juan Boero

6

คำตอบที่ได้รับการยอมรับทำงานได้ดีสำหรับสคริปต์ที่ยังไม่ได้รับ symlinked อื่น ๆ $PATHเช่นเข้า

#!/bin/bash
cd "$(dirname "$0")"

อย่างไรก็ตามหากสคริปต์นั้นทำงานผ่าน symlink

ln -sv ~/project/script.sh ~/bin/; 
~/bin/script.sh

สิ่งนี้จะ cd ลงใน~/bin/ไดเรกทอรีและไม่ใช่~/project/ไดเรกทอรีซึ่งอาจทำให้สคริปต์ของคุณเสียหายหากวัตถุประสงค์ของการcdรวมการอ้างอิงที่เกี่ยวข้องกับ~/project/

คำตอบที่ปลอดภัย symlink อยู่ด้านล่าง:

#!/bin/bash
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

readlink -f จำเป็นต้องแก้ไขพา ธ สัมบูรณ์ของไฟล์ที่ลิงก์อาจเกิดขึ้น

ต้องใช้เครื่องหมายคำพูดเพื่อรองรับไฟล์พา ธ ที่อาจมีช่องว่าง (การปฏิบัติที่ไม่ดี แต่ไม่ปลอดภัยที่จะถือว่านี่ไม่ใช่กรณี)


4

ดูเหมือนว่าสคริปต์นี้จะทำงานสำหรับฉัน:

#!/bin/bash
mypath=`realpath $0`
cd `dirname $mypath`
pwd

บรรทัดคำสั่ง pwd สะท้อนตำแหน่งของสคริปต์เป็นไดเรกทอรีการทำงานปัจจุบันไม่ว่าฉันจะเรียกใช้จากที่ใด


3
realpathไม่น่าจะติดตั้งได้ทุกที่ ซึ่งอาจไม่สำคัญขึ้นอยู่กับสถานการณ์ของ OP
bstpierre

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

2
dist ใดที่ไม่มี realpath ติดตั้งโดยค่าเริ่มต้น อูบุนตูมีแล้ว
kenorb


1
cd "`dirname $(readlink -f ${0})`"

คุณช่วยอธิบายคำตอบของคุณได้ไหม
ซูลู

1
แม้ว่ารหัสนี้อาจช่วยในการแก้ปัญหา แต่ก็ไม่ได้อธิบายว่าทำไมและ / หรือวิธีการตอบคำถาม การให้บริบทเพิ่มเติมนี้จะช่วยเพิ่มมูลค่าทางการศึกษาในระยะยาวอย่างมีนัยสำคัญ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายรวมถึงข้อ จำกัด และข้อสมมติฐานที่ใช้
Toby Speight


-6

หากคุณเพียงแค่ต้องพิมพ์ไดเรกทอรีทำงานปัจจุบันคุณสามารถทำตามนี้

$ vim test

#!/bin/bash
pwd
:wq to save the test file.

ให้สิทธิ์ดำเนินการ:

chmod u+x test

จากนั้นเรียกใช้งานสคริปต์จาก./testนั้นคุณสามารถดูไดเรกทอรีการทำงานปัจจุบัน


2
คำถามคือวิธีการตรวจสอบให้แน่ใจว่าสคริปต์ทำงานในไดเรกทอรีของตัวเองรวมถึงถ้าไม่ใช่ไดเรกทอรีทำงาน ดังนั้นนี่ไม่ใช่คำตอบ แล้วทำไมทำเช่นนี้แทนที่จะเป็นเพียงแค่ ... การทำงานpwd? ดูเหมือนว่ากดปุ่มจำนวนมากที่สูญเปล่าไปแล้วสำหรับฉัน
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.