Apakah mungkin untuk melakukan substitusi perintah shell tanpa menggunakan subshell?

11

Saya memiliki skenario yang meminta penggantian perintah tanpa menggunakan subshell. Saya memiliki konstruk seperti ini:

pushd $(mktemp -d)

Sekarang saya ingin keluar dan menghapus direktori sementara dalam sekali jalan:

rmdir $(popd)

Namun itu tidak berhasil karena popdtidak mengembalikan direktori muncul (itu mengembalikan direktori baru, sekarang saat ini) dan juga karena dilakukan dalam subkulit.

Sesuatu seperti

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

akan mengembalikan direktori yang muncul tetapi tidak dapat digunakan seperti ini:

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

karena popdhanya akan mempengaruhi subkulit. Yang dibutuhkan adalah kemampuan untuk melakukan ini:

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

tapi itu sintaks yang tidak valid. Apakah mungkin untuk mencapai efek ini?

(catatan: Saya tahu saya bisa menyimpan direktori sementara dalam sebuah variabel; Saya berusaha menghindari keharusan untuk melakukannya dan mempelajari sesuatu yang baru dalam proses!)

starfry
sumber
1
Saya akan menyimpan direktori ke variabel; dengan cara itu, seorang trappawang dapat membersihkan direktori jika prosesnya ditusuk oleh sinyal.
hujan
Jawabannya adalah tidak .
h0tw1r3
Sepertinya pada fish, setara dengan substitusi perintah (),, mengubah folder shell luar. Ini biasanya menjengkelkan, tetapi dalam kasus seperti ini itu berguna, saya yakin.
trysis

Jawaban:

10

Pilihan judul pertanyaan Anda agak membingungkan.

pushd/ popd, cshfitur yang disalin oleh bashdan zsh, adalah cara untuk mengelola tumpukan direktori yang diingat.

pushd /some/dir

mendorong direktori kerja saat ini ke tumpukan, dan kemudian mengubah direktori kerja saat ini (dan kemudian mencetak /some/dirdiikuti oleh konten tumpukan itu (dipisahkan ruang).

popd

mencetak isi tumpukan (sekali lagi, dipisahkan dengan ruang) dan kemudian berubah ke elemen atas tumpukan dan mengeluarkannya dari tumpukan.

(juga berhati-hatilah bahwa beberapa direktori akan diwakili sana dengan mereka ~/xatau ~user/xnotasi).

Jadi jika tumpukan saat ini memiliki /adan /b, direktori saat ini adalah /heredan Anda sedang menjalankan:

 pushd /tmp/whatever
 popd

pushdakan mencetak /tmp/whatever /here /a /bdan popdakan menampilkan /here /a /b, bukan /tmp/whatever. Itu independen menggunakan substitusi perintah atau tidak. popdtidak dapat digunakan untuk mendapatkan path dari direktori sebelumnya, dan secara umum outputnya tidak dapat diproses pasca (lihat array $dirstackatau $DIRSTACKbeberapa shell meskipun untuk mengakses elemen-elemen dari stack direktori)

Mungkin Anda ingin:

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

Atau

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

Padahal, saya akan menggunakan:

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

Bagaimanapun, pushd "$(mktemp -d)"tidak berjalan pushddalam subkulit. Jika ya, itu tidak bisa mengubah direktori kerja. Itu mktempyang berjalan dalam subkulit. Karena itu adalah perintah yang terpisah, itu harus dijalankan dalam proses yang terpisah. Itu menulis outputnya pada pipa, dan proses shell membacanya di ujung pipa yang lain.

ksh93 dapat menghindari proses terpisah ketika perintah dibangun di dalam, tetapi bahkan di sana, itu masih sebuah subkulit (lingkungan kerja yang berbeda) yang kali ini ditiru daripada mengandalkan pada lingkungan terpisah yang biasanya disediakan oleh forking. Misalnya, dalam ksh93,, a=0; echo "$(a=1; echo test)"; echo "$a"tidak ada percabangan yang terlibat, tetapi masih echo "$a"menghasilkan 0.

Di sini, jika Anda ingin menyimpan output mktempdalam variabel, pada saat yang sama ketika Anda meneruskannya ke pushd, dengan zsh, Anda bisa melakukan:

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

Dengan kerang mirip Bourne lainnya:

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

Atau untuk menggunakan output $(mktemp -d)beberapa kali tanpa secara eksplisit menyimpannya dalam variabel, Anda bisa menggunakan zshfungsi anonim:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
Stéphane Chazelas
sumber
Saya mengerti pushddan popdbekerja seperti yang Anda jelaskan dan bahwa mereka independen dari penggantian komando - tidak ada kebingungan di sana! Namun, Anda menjawab dengan masalah sendiri dengan mengungkapkan $OLDPWD. Saya bisa melakukannya popd; rmdir $OLDPWD. Itulah jawaban untuk masalah saya - semua yang lain hanya menegaskan apa yang saya pikirkan. Substitusi perintah tampaknya merupakan cara untuk menyelesaikannya tetapi bukan karena subkulit dan Anda tidak dapat melakukan substitusi perintah tanpa subkulit, Jadi terima kasih karena mengungkapkan OLDPWD - itulah yang saya butuhkan!
starfry
@ starfry, rmdir $(popd)menyebabkan popddijalankan dalam sub-shell yang berarti tidak akan mengubah direktori saat ini, tetapi bahkan jika itu tidak berjalan dalam subkulit, output popdtidak akan menjadi direktori sementara, itu akan menjadi daftar yang dipisahkan oleh ruang direktori tidak termasuk direktori temp. Di situlah saya mengatakan Anda bingung.
Stéphane Chazelas
tetapi saya pikir saya telah mengatakan bahwa dalam pertanyaan saya: "popd tidak mengembalikan direktori muncul (itu mengembalikan direktori baru, sekarang saat ini) dan juga karena itu dilakukan dalam subkulit." Memang itu mengembalikan daftar, tetapi yang pertama dalam daftar adalah yang saya maksud.
starfry
@ starfry, oke maaf. Katakanlah saya yang bingung dengan judul pertanyaan dan tidak cukup membaca sisanya.
Stéphane Chazelas
baik jawaban Anda masih bagus dan mengarahkan saya ke arah yang benar. Saya tidak pernah tahu tentang variabel shell itu OLDPWD. Saya harus ingat suatu hari membaca man bashdari ujung ke ujung!
starfry
2

Anda dapat memutuskan tautan direktori terlebih dahulu, sebelum meninggalkannya:

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

atau

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

tetapi perhatikan bahwa pushddan popdbenar-benar alat untuk shell interaktif, bukan untuk skrip (itulah sebabnya mereka sangat cerewet; perintah scripting sungguhan akan diam saat berhasil).

Toby Speight
sumber
0

Di bash, dirsberikan daftar direktori yang diingat dengan metode pushd / popd.

Juga, dirs -1cetak direktori terakhir yang termasuk dalam daftar.

Jadi, untuk menghapus direktori yang dibuat sebelumnya dengan mengeksekusi pushd $(mktmp -d), gunakan:

rmdir $(dirs -1)

Dan kemudian, popddirektori yang sudah dihapus dari daftar:

popd > /dev/null

Semua dalam satu baris:

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

Dan menambahkan opsi (-l) untuk menghindari ~/xatau ~user/xnotasi:

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

Yang sangat mirip dengan garis yang Anda minta.
Kecuali bahwa saya tidak akan menggunakan &>karena akan menyembunyikan pelaporan kesalahan oleh popd.

Catatan: Direktori akan tetap setelah rmdirkarena pada pwdsaat itu. Dan akan benar-benar dibatalkan tautannya setelah popdperintah (tidak ada tautan yang tersisa digunakan).


Ada opsi untuk digunakan, untuk shell yang mendukung variabel "OLDPWD" (kebanyakan shell seperti bourne: ksh, bash, zsh miliki $OLDPWD). Sangat menarik untuk dicatat bahwa ksh tidak mengimplementasikan dirs, pod, pushd secara default (lksh, dash dan lainnya juga tidak memiliki popd yang tersedia, jadi, tidak dapat digunakan di sini):

popd && rmdir "$OLDPWD"

Atau, lebih idiomatis (daftar kerang yang sama seperti di atas):

popd && rmdir ~-
Komunitas
sumber
Masalah dengan ini, dan apa yang saya coba selesaikan, adalah Anda tidak bisa rmdirdirektori saat ini di mana Anda akan berada ketika melakukan ini. Anda perlu popdmelakukan sebelum melakukan rmdirtetapi Anda harus tahu apa yang harus dihapus dan popdtidak memberi tahu Anda. Saya, seperti Anda, pikir dirs -l -1adalah jawabannya tetapi sejak itu menemukan bahwa jawabannya sebenarnya untuk digunakan $OLDPWD.
starfry
@starfry Saya percaya bahwa jawaban terpendek adalah untuk digunakan: popd && rmdir ~-.
@ starfry Anda sebenarnya dapat menghapus direktori saat ini, tidak masalah, asalkan kosong (tanpa memaksa menghapus). Anda bahkan bisa menelepon dengan rmdir "$PWD"benar. Juga, direktori saat ini harus menjadi direktori yang dibuat oleh mktmp -d(jika pushd $(mktemp -d)dieksekusi sebelumnya) dan yang pushdmemindahkan pwd. Jadi, ya, setelah pushd dan sebelum popd, direktori yang dibuat disimpan $(dirs -1), saya gagal melihat masalah.