Có thể thực hiện thay thế lệnh shell mà không cần sử dụng một subshell?


11

Tôi có một kịch bản gọi thay thế lệnh mà không sử dụng một lớp con. Tôi có một cấu trúc như thế này:

pushd $(mktemp -d)

Bây giờ tôi muốn thoát và xóa thư mục tạm thời trong một lần:

rmdir $(popd)

Tuy nhiên, điều đó không hoạt động vì popdkhông trả về thư mục đã bật (nó trả về thư mục mới, hiện tại, hiện tại) và cũng vì nó được thực hiện trong một lớp con.

Cái gì đó như

dirs -l -1 ; popd &> /dev/null

sẽ trả về thư mục popped nhưng nó không thể được sử dụng như thế này:

rmdir $(dirs -l -1 ; popd &> /dev/null)

bởi vì ý popdchí chỉ ảnh hưởng đến subshell. Những gì được gọi là khả năng để làm điều này:

rmdir { dirs -l -1 ; popd &> /dev/null; }

nhưng đó là cú pháp không hợp lệ. Có thể đạt được hiệu ứng này?

(lưu ý: Tôi biết tôi có thể lưu thư mục tạm thời trong một biến; Tôi đã cố gắng tránh sự cần thiết phải làm như vậy và tìm hiểu một cái gì đó mới trong quy trình!)


1
Tôi sẽ lưu thư mục vào một biến; bằng cách đó, một traptrình xử lý có thể dọn sạch thư mục nếu quá trình được chọc bởi tín hiệu.
thrig

Câu trả lời là không .
h0tw1r3

Có vẻ như trên fish, tương đương với thay thế lệnh (), thay đổi thư mục của lớp vỏ bên ngoài. Nó thường gây phiền nhiễu, nhưng trong những trường hợp như thế này thì nó hữu ích, tôi chắc chắn.
phân tích

Câu trả lời:


10

Sự lựa chọn tiêu đề của câu hỏi của bạn là một chút bối rối.

pushd/ popd, một cshtính năng được sao chép bởi bashzsh, là một cách để quản lý một chồng các thư mục đã nhớ.

pushd /some/dir

đẩy thư mục làm việc hiện tại lên một ngăn xếp, và sau đó thay đổi thư mục làm việc hiện tại (và sau đó in /some/dirtheo nội dung của ngăn xếp đó (được phân tách bằng dấu cách).

popd

in nội dung của ngăn xếp (một lần nữa, khoảng cách được phân tách) và sau đó thay đổi thành phần tử trên cùng của ngăn xếp và bật nó từ ngăn xếp.

(cũng hãy cẩn thận rằng một số thư mục sẽ được trình bày ở đó với ký hiệu ~/xhoặc ~user/xký hiệu của chúng).

Vì vậy, nếu ngăn xếp hiện có /a/b, thư mục hiện tại đang /herevà bạn đang chạy:

 pushd /tmp/whatever
 popd

pushdsẽ in /tmp/whatever /here /a /bpopdsẽ xuất /here /a /b, không /tmp/whatever. Điều đó độc lập với việc sử dụng thay thế lệnh hay không. popdkhông thể được sử dụng để có được đường dẫn của thư mục trước đó và nói chung, đầu ra của nó không thể được xử lý (xem $dirstackhoặc $DIRSTACKmảng của một số shell mặc dù để truy cập các phần tử của ngăn xếp thư mục đó)

Có thể bạn muốn:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"

Hoặc là

cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

Mặc dù, tôi sẽ sử dụng:

tmpdir=$(mktemp -d) || exit
(
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
)
rmdir "$tmpdir"

Trong mọi trường hợp, pushd "$(mktemp -d)"không chạy pushdtrong một subshell. Nếu có, nó không thể thay đổi thư mục làm việc. Đó là mktempchạy trong một subshell. Vì nó là một lệnh riêng biệt, nó phải chạy trong một quy trình riêng. Nó viết đầu ra của nó trên một đường ống và quá trình vỏ đọc nó ở đầu kia của đường ống.

ksh93 có thể tránh quá trình riêng biệt khi lệnh được xây dựng, nhưng ngay cả ở đó, nó vẫn là một khung con (một môi trường làm việc khác) mà lần này được mô phỏng thay vì dựa vào môi trường riêng biệt thường được cung cấp bằng cách giả mạo. Ví dụ, trong ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"không có ngã ba nào được tham gia, nhưng vẫn echo "$a"xuất ra kết quả 0.

Ở đây, nếu bạn muốn lưu trữ đầu ra của mktempmột biến, cùng lúc với việc bạn chuyển nó tới pushd, với zsh, bạn có thể làm:

pushd ${tmpdir::="$(mktemp -d)"}

Với các vỏ giống như Bourne khác:

unset tmpdir
pushd "${tmpdir=$(mktemp -d)}"

Hoặc để sử dụng đầu ra $(mktemp -d)nhiều lần mà không lưu trữ rõ ràng trong một biến, bạn có thể sử dụng zshcác hàm ẩn danh:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"

Tôi hiểu pushdpopdlàm việc như bạn mô tả và họ độc lập với việc thay thế lệnh - không có sự nhầm lẫn ở đó! Tuy nhiên, bạn đã trả lời bằng vấn đề của riêng mình bằng cách tiết lộ $OLDPWD. Tôi có thể làm popd; rmdir $OLDPWD. Đó là câu trả lời cho vấn đề của tôi - mọi thứ khác chỉ xác nhận những gì tôi nghĩ. Thay thế lệnh dường như là một cách để giải quyết nó nhưng không phải vì lớp con và bạn không thể thay thế lệnh mà không có lớp con, vì vậy cảm ơn vì đã tiết lộ OLDPWD - đó chính xác là những gì tôi cần!
starfry

@starfry, rmdir $(popd)nguyên nhân popdchạy trong lớp vỏ phụ có nghĩa là nó sẽ không thay đổi thư mục hiện tại, nhưng ngay cả khi nó không chạy trong một lớp con, đầu ra của popdsẽ không phải là thư mục tạm thời, nó sẽ là một danh sách được phân tách bằng dấu cách của các thư mục không bao gồm thư mục tạm thời. Đó là nơi tôi đang nói bạn bối rối.
Stéphane Chazelas

nhưng tôi nghĩ rằng tôi đã nói rằng trong câu hỏi của tôi: "popd không trả về thư mục popped (nó trả về thư mục mới, hiện tại, hiện tại) và cũng bởi vì nó được thực hiện trong một subshell." Phải thừa nhận rằng nó trả về một danh sách, nhưng cái đầu tiên trong danh sách là cái tôi đang đề cập đến.
starfry

@starfry, xin lỗi. Hãy nói rằng tôi là người bối rối bởi tiêu đề của câu hỏi và đã không đọc phần còn lại đủ cẩn thận.
Stéphane Chazelas

Vâng, câu trả lời của bạn vẫn tốt và chỉ cho tôi đi đúng hướng. Tôi không bao giờ biết về biến vỏ đó OLDPWD. Tôi phải nhớ một ngày nào đó đọc man bashtừ đầu đến cuối!
starfry

2

Bạn có thể hủy liên kết thư mục trước, trước khi rời khỏi nó:

rmdir "$(pwd -P)" && popd

hoặc là

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

nhưng lưu ý rằng pushdpopdthực sự là các công cụ cho các shell tương tác, không phải cho các tập lệnh (đó là lý do tại sao chúng rất trò chuyện; các lệnh script thực sự im lặng khi chúng thành công).


0

Trong bash, dirscung cấp một danh sách các thư mục được ghi nhớ bằng phương thức Pushd / popd.

Ngoài ra, dirs -1in thư mục cuối cùng có trong danh sách.

Vì vậy, để xóa thư mục đã tạo trước đó bằng cách thực thi pushd $(mktmp -d), hãy sử dụng:

rmdir $(dirs -1)

Và sau đó, thư popdmục đã bị xóa khỏi danh sách:

popd > /dev/null

Tất cả trong một dòng:

rmdir $(dirs -1); popd > /dev/null

Và thêm tùy chọn (-l) để tránh ~/xhoặc ~user/xký hiệu:

rmdir $(dirs -l -1); popd > /dev/null

Đó là tương tự đáng chú ý với dòng bạn yêu cầu.
Ngoại trừ việc tôi sẽ không sử dụng &>vì nó sẽ ẩn bất kỳ báo cáo lỗi nào popd.

Lưu ý: Thư mục sẽ vẫn còn sau rmdirvì nó là pwdtại thời điểm đó. Và sẽ thực sự được hủy liên kết sau popdlệnh (không sử dụng các liên kết còn lại).


Có một tùy chọn để sử dụng, đối với các shell hỗ trợ biến "OLDPWD" (hầu hết các shell giống như bourne: ksh, bash, zsh đều có $OLDPWD). Thật thú vị khi lưu ý rằng ksh không triển khai dir, pod, Pushd theo mặc định (lksh, dash và những thứ khác cũng không có sẵn popd, vì vậy, không thể sử dụng ở đây):

popd && rmdir "$OLDPWD"

Hoặc, thành ngữ hơn (cùng danh sách các shell như trên):

popd && rmdir ~-

Vấn đề với điều này, và điều tôi đang cố gắng giải quyết, là bạn không thể rmdirthư mục hiện tại là nơi bạn sẽ làm khi làm điều này. Bạn cần phải popdthực hiện trước khi thực hiện rmdirnhưng bạn cần biết những gì cần loại bỏ và popdkhông cho bạn biết điều đó. Tôi cũng như bạn, nghĩ dirs -l -1là câu trả lời nhưng từ đó đã phát hiện ra rằng câu trả lời thực sự là để sử dụng $OLDPWD.
starfry

@starfry Tôi tin rằng câu trả lời ngắn nhất là sử dụng : popd && rmdir ~-.

@starfry Bạn thực sự có thể xóa thư mục hiện tại, không có vấn đề gì, miễn là nó trống (mà không buộc xóa). Bạn thậm chí có thể gọi rmdir "$PWD"tất cả các quyền. Ngoài ra, thư mục hiện tại phải là thư mục được tạo bởi mktmp -d(nếu pushd $(mktemp -d)được thực hiện trước) và pushddi chuyển pwd. Vì vậy, vâng, sau pushdtrước popd, thư mục đã tạo được lưu trữ $(dirs -1), tôi không thấy bất kỳ vấn đề nào.
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.