Làm thế nào để thiết lập thư mục làm việc hiện tại vào thư mục của tập lệnh?


569

Tôi đang viết một kịch bản bash. Tôi cần thư mục làm việc hiện tại để luôn là thư mục mà tập lệnh được đặt.

Hành vi mặc định là thư mục làm việc hiện tại trong tập lệnh là shell mà tôi chạy nó, nhưng tôi không muốn hành vi này.


Bạn đã xem xét việc đặt một tập lệnh bao bọc ở đâu đó như / usr / bin để cd vào thư mục thích hợp (mã hóa cứng) và sau đó thực thi tập lệnh của bạn chưa?
Dagg Nợi

2
Tại sao bạn cần thư mục của kịch bản? Có lẽ có một cách tốt hơn để giải quyết vấn đề tiềm ẩn.
bstpierre

12
Tôi chỉ muốn chỉ ra rằng hành vi mà bạn gọi là "rõ ràng là không mong muốn" trên thực tế là hoàn toàn cần thiết - nếu tôi chạy, myscript path/to/filetôi hy vọng tập lệnh sẽ đánh giá đường dẫn / đến / tệp so với thư mục hiện tại của tôi, chứ không phải bất kỳ thư mục nào xảy ra. để được định vị. Ngoài ra, bạn sẽ có những gì xảy ra đối với một tập lệnh chạy với ssh remotehost bash < ./myscriptcâu hỏi thường gặp về BASH?
Gordon Davisson


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

Câu trả lời:


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

7
dirname trả về '.' khi sử dụng bash trong Windows. Vì vậy, câu trả lời của Paul là tốt hơn.
Tvaroh

7
Cũng trả về '.' trong Mac OSX
Ben Clayton

4
Điều đáng chú ý là mọi thứ có thể bị phá vỡ nếu một liên kết tượng trưng chiếm một phần $0. Trong kịch bản của bạn, bạn có thể mong đợi, ví dụ, ../../đề cập đến thư mục hai cấp trên vị trí của tập lệnh, nhưng điều này không nhất thiết là trường hợp nếu các liên kết tượng trưng đang hoạt động.
Brian Gordon

20
Nếu bạn gọi tập lệnh là ./script, .là thư mục chính xác, và thay đổi thành tập lệnh .cũng sẽ kết thúc trong chính thư mục scriptđược đặt, tức là trong thư mục làm việc hiện tại.
ndim

6
Nếu bạn chạy tập lệnh từ thư mục hiện tại như vậy bash script.sh, thì giá trị của $0script.sh. Cách duy nhất cdlệnh sẽ "làm việc" cho bạn là vì bạn không quan tâm đến các lệnh thất bại. Nếu bạn đã sử dụng set -o errexit(aka set -e:) để đảm bảo rằng tập lệnh của bạn không thổi qua các lệnh thất bại, thì điều này sẽ KHÔNG hoạt động vì đó cd script.shlà một lỗi. Đáng tin cậy [bash cụ thể] Cách thứ nhất làcd "$(dirname ${BASH_SOURCE[0]})"
Bruno Bronosky

322

Sau đây cũng hoạt động:

cd "${0%/*}"

Cú pháp được mô tả kỹ lưỡng trong câu trả lời StackOverflow này .


17
Giải thích cách thức hoạt động của nó: stackoverflow.com/questions/6393551/ từ
kenorb

25
Đừng quên gửi kèm trong dấu ngoặc kép nếu đường dẫn chứa khoảng trắng. tức là cd "$ {0% / *}"
H.Rabiee

17
Điều này không thành công nếu tập lệnh được gọi với bash script.sh- $0sẽ chỉ là tên của tệp
Chris Watts

17
Tôi muốn nói câu trả lời này quá ngắn gọn. Bạn hầu như không học được gì về cú pháp. Một số mô tả về cách đọc này sẽ hữu ích.
fraxture

2
Thủ thuật này không sinh ra một subshell quá tốt vì vậy rất tốt cho những kẻ cuồng tốc độ.
teknopaul

184

Hãy thử các lớp lót đơn giản sau:


Đối với tất cả UNIX / OSX / Linux

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

Bash

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

Lưu ý: Dấu gạch ngang kép (-) được sử dụng trong các lệnh để biểu thị sự kết thúc của các tùy chọn lệnh, vì vậy các tệp có dấu gạch ngang hoặc các ký tự đặc biệt khác sẽ không phá vỡ lệnh.

Lưu ý: Trong Bash, sử dụng có ${BASH_SOURCE[0]}lợi cho $0, nếu không đường dẫn có thể bị phá vỡ khi tìm nguồn cung ứng ( source/ .).


Đối với Linux, Mac và * BSD khác:

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

Lưu ý: realpathnên được cài đặt trong bản phân phối Linux phổ biến nhất theo mặc định (như Ubuntu), nhưng trong một số phần có thể bị thiếu, vì vậy bạn phải cài đặt nó.

Lưu ý: Nếu bạn đang sử dụng Bash, hãy sử dụng ${BASH_SOURCE[0]}để ủng hộ $0, nếu không, đường dẫn có thể bị phá vỡ khi tìm nguồn cung ứng ( source/ .).

Nếu không, bạn có thể thử một cái gì đó như thế (nó sẽ sử dụng công cụ hiện có đầu tiên):

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

Đối với Linux cụ thể:

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

Sử dụng liên kết đọc GNU trên * BSD / Mac:

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

Lưu ý: Bạn cần phải coreutilscài đặt (ví dụ: 1. Cài đặt Homebrew , 2. brew install coreutils).


Trong bash

Trong bash, bạn có thể sử dụng Mở rộng tham số để đạt được điều đó, như:

cd "${0%/*}"

nhưng nó không hoạt động nếu tập lệnh được chạy từ cùng một thư mục.

Ngoài ra, bạn có thể xác định chức năng sau trong bash:

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

Hàm này có 1 đối số. Nếu đối số đã có đường dẫn tuyệt đối, hãy in nó như hiện tại, nếu không thì in $PWDbiến + đối số tên tệp (không có ./tiền tố).

hoặc đây là phiên bản lấy từ .bashrctệp 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"
}

Liên quan:

Xem thêm:

Làm cách nào tôi có thể có được hành vi của readlink -f của GNU trên máy Mac?


4
một câu trả lời tốt hơn nhiều so với những câu hỏi phổ biến vì nó giải quyết các liên kết syslink và các tài khoản cho các hệ điều hành khác nhau. Cảm ơn!
Dennis

Cảm ơn câu trả lời này. Công cụ hàng đầu hoạt động trên máy Mac của tôi ... nhưng công tắc - làm gì trong cplệnh, @kenorb?
TobyG

3
Dấu gạch ngang kép ( --) được sử dụng trong các lệnh để biểu thị sự kết thúc của các tùy chọn lệnh, vì vậy các tệp chứa dấu gạch ngang hoặc các ký tự đặc biệt khác sẽ không phá vỡ lệnh. Thử ví dụ: tạo tệp qua touch "-test"touch -- -testsau đó xóa tệp quarm "-test"rm -- -test, và thấy sự khác biệt.
kenorb

1
Tôi sẽ xóa upvote của mình nếu tôi có thể: realpath bị phản đối unix.stackexchange.com/questions/136494/ mẹo
lsh

1
@lsh Nó chỉ nói rằng realpathgói Debian không được dùng nữa, không phải GNU realpath. Nếu bạn nghĩ rằng nó không rõ ràng, bạn có thể đề nghị chỉnh sửa.
kenorb

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

Dễ thôi. Nó hoạt động.


1
Đây phải là câu trả lời được chấp nhận. Hoạt động trên Ubuntu OSX và Linux.
David Rissato Cruz

5
Điều này hoạt động rất tốt khi tập lệnh B có nguồn gốc từ tập lệnh A khác và bạn cần sử dụng các đường dẫn liên quan đến tập lệnh B. Cảm ơn bạn.
Enrico

5
Điều này cũng hoạt động trong Cygwin cho những người trong chúng ta không may phải chạm vào Windows.
Bruno Bronosky

3
Hoặccd "${BASH_SOURCE%/*}" || exit
caw

Hoạt động trên macOS Mojave
Juan Boero 30/07/19

6

Câu trả lời được chấp nhận hoạt động tốt cho các tập lệnh chưa được liên kết ở nơi khác, chẳng hạn như vào $PATH.

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

Tuy nhiên, nếu tập lệnh được chạy qua một liên kết tượng trưng,

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

Điều này sẽ cd vào ~/bin/thư mục chứ không phải ~/project/thư mục, có thể sẽ phá vỡ tập lệnh của bạn nếu mục đích củacd nó là bao gồm các phụ thuộc liên quan đến~/project/

Câu trả lời an toàn cho symlink là dưới đây:

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

readlink -f được yêu cầu để giải quyết đường dẫn tuyệt đối của tệp có khả năng liên kết.

Các trích dẫn được yêu cầu để hỗ trợ các filepath có khả năng chứa khoảng trắng (thực tiễn xấu, nhưng không an toàn khi cho rằng điều này sẽ không xảy ra)


4

Kịch bản này dường như làm việc cho tôi:

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

Dòng lệnh pwd lặp lại vị trí của tập lệnh là thư mục làm việc hiện tại bất kể tôi chạy nó từ đâu.


3
realpathkhông có khả năng được cài đặt ở khắp mọi nơi. Điều này có thể không quan trọng, tùy thuộc vào tình hình của OP.
bstpierre

Hệ thống của tôi không có realpathnhưng nó có readlinkvẻ giống nhau.
Tạm dừng cho đến khi có thông báo mới.

2
Mà dist không có cài đặt realpath mặc định? Ubuntu có nó.
kenorb


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

Bạn có thể giải thích câu trả lời của bạn xin vui lòng?
Zulu

1
Mặc dù mã này có thể giúp giải quyết vấn đề, nhưng nó không giải thích được tại sao và / hoặc cách nó trả lời câu hỏi. Cung cấp bối cảnh bổ sung này sẽ cải thiện đáng kể giá trị giáo dục lâu dài của nó. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích, bao gồm những hạn chế và giả định được áp dụng.
Toby Speight

-5
echo $PWD

NKT là một biến môi trường.


5
Điều này chỉ cung cấp cho thư mục được gọi từ, không phải thư mục chứa tập lệnh.
dagelf

-6

Nếu bạn chỉ cần in thư mục làm việc hiện tại thì bạn có thể làm theo điều này.

$ vim test

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

Cho phép thực thi:

chmod u+x test

Sau đó thực thi kịch bản bằng cách ./testsau đó bạn có thể thấy thư mục làm việc hiện tại.


2
Câu hỏi là làm thế nào để đảm bảo tập lệnh chạy trong thư mục riêng của nó , bao gồm cả nếu đó không phải là thư mục làm việc. Vì vậy, đây không phải là một câu trả lời. Dù sao, tại sao làm điều này thay vì chỉ ... chạy pwd? Có vẻ như rất nhiều nhấn phím lãng phí cho tôi.
gạch dưới
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.