Чи можливо виконати підстановку команди оболонки без використання підшару?


11

У мене є сценарій, який вимагає заміни команд без використання підшару. У мене є така конструкція:

pushd $(mktemp -d)

Тепер я хочу вийти та видалити тимчасовий каталог за один раз:

rmdir $(popd)

Однак це не працює, оскільки popdне повертає спливаючу папку (вона повертає нову, теперішню, каталог), а також тому, що вона виконується в підпакеті.

Щось на зразок

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

поверне popped каталог, але його не можна використовувати так:

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

тому що popdзаповіт вплине лише на нижню частину. До чого потрібно закликати:

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

але це недійсний синтаксис. Чи можливо досягти цього ефекту?

(зауважте: я знаю, що я можу зберегти тимчасовий каталог у змінній; я намагався уникнути необхідності цього і дізнатися щось нове в процесі!)


1
Я збережу каталог у змінну; таким чином, trapобробник може очистити каталог, якщо процес буде викликаний сигналом.
тригз

Відповідь - ні .
h0tw1r3

Схоже, що на fish, еквівалент підстановки команд (), змінюється папка зовнішньої оболонки. Зазвичай це дратує, але у таких випадках це корисно, я впевнений.
trysis

Відповіді:


10

Вибір назви вашого питання трохи заплутаний.

pushd/ popd, cshфункція, скопійована на bashта zsh, є способом управління стеком запам’ятованих каталогів.

pushd /some/dir

висуває поточний робочий каталог на стек, а потім змінює поточний робочий каталог (а потім друкує, /some/dirа потім вміст цього стека (розділений пробілом).

popd

друкує вміст стеку (знову ж таки, розділений пробіл), а потім змінює верхній елемент стека і виводить його зі стека.

(також майте на увазі, що деякі каталоги будуть представлені там зі своїми позначеннями ~/xабо ~user/xпозначеннями).

Отже, якщо стек на даний момент є /aі /b, поточний каталог є /hereі ви працюєте:

 pushd /tmp/whatever
 popd

pushdбуде друкувати /tmp/whatever /here /a /bі popdвиводить /here /a /b, не /tmp/whatever. Це незалежно від використання підстановки команд чи ні. popdне може бути використаний для отримання шляху попереднього каталогу, і загалом його вихід не може бути оброблений після публікації (див. $dirstackабо $DIRSTACKмасив деяких оболонок, хоча для доступу до елементів цього стека каталогу)

Можливо, ви хочете:

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

Або

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

Хоча я б використав:

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

У будь-якому випадку, pushd "$(mktemp -d)"він не працює pushdв передпласті. Якби це зробити, він не міг би змінити робочий каталог. Це те, mktempщо працює в нижній частині. Оскільки це окрема команда, вона повинна запускатися в окремому процесі. Він записує свій вихід на трубу, а процес оболонки зчитує його на іншому кінці труби.

ksh93 може уникнути окремого процесу, коли команда вбудована, але навіть там, як і раніше, це підзарядка (інше робоче середовище), яка цього разу емулюється, а не покладається на окреме середовище, яке зазвичай забезпечується розгортанням. Наприклад, у ksh93, a=0; echo "$(a=1; echo test)"; echo "$a"жодна форка не задіяна, але все одно echo "$a"виводиться 0.

Тут, якщо ви хочете зберегти висновок mktempзмінної у той самий час, коли ви передаєте її pushd, з zsh, ви можете зробити:

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

З іншими снарядами, схожими на Борна:

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

Або використовувати висновок $(mktemp -d)кілька разів без явного зберігання його в змінній, ви можете використовувати zshанонімні функції:

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

Я розумію pushdі popdпрацюю так, як ви описуєте, і що вони не залежать від заміни команд - ніякої плутанини немає! Однак ти відповів власною проблемою, розкривши $OLDPWD. Я можу зробити popd; rmdir $OLDPWD. Це відповідь на мою проблему - все інше просто підтверджує те, що я думав. Зміна підстановки команд є способом її вирішення, але це не через підзарядку, і ви не можете виконати підміну команд без підрозділу, тож дякую за виявлення OLDPWD - саме це мені і потрібно!
starfry

@starfry, rmdir $(popd)призводить popdдо запуску в підколонці, що означає, що він не змінить поточний каталог, але навіть якщо він не запускався в підпакеті, вихід popdне буде тимчасовим каталогом, це буде розділений пробілом список каталогів, що не включають цей темп. Я тут кажу, що ви розгублені.
Стефан Шазелас

але я подумав, що це сказав у своєму запитанні: "popd не повертає popped каталог (він повертає нову, теперішню, каталог), а також тому, що він виконується в нижній частині". Справді, він повертає список, але першим у списку є той, про який я мав на увазі.
starfry

@starfry, добре вибачте. Скажімо, я був збентежений назвою питання, а решту не прочитав ретельно достатньо уважно.
Стефан Шазелас

ну ваша відповідь була все-таки хороша і вказала мені в правильному напрямку. Я ніколи не знав про цю змінну оболонки OLDPWD. Треба пам’ятати, щоб одного дня читати man bashвід кінця до кінця!
starfry

2

Ви можете спочатку від’єднати каталог, перш ніж залишати його:

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

або

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

але зверніть увагу , що pushdі popdнасправді інструменти для інтерактивних оболонок, а не для скриптів (саме тому вони настільки балакучі; команди реальних сценаріїв мовчать , коли їм вдається).


0

У bash dirsнадайте список запам’ятованих каталогів методом pushd / popd.

Також dirs -1друкує останній каталог, включений до списку.

Отже, для видалення каталогу, створеного раніше виконанням pushd $(mktmp -d), використовуйте:

rmdir $(dirs -1)

А потім popdвже видалений каталог зі списку:

popd > /dev/null

Все в одному рядку:

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

І додавання параметра (-l), щоб уникнути ~/xабо ~user/xпозначення:

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

Що надзвичайно схоже на лінію, яку ви просили.
За винятком того, що я б не використовував, &>оскільки це приховувало б повідомлення про помилки від popd.

Примітка: Каталог залишиться після того, rmdirяк він є pwdв цій точці. І буде фактично від’єднано після popdкоманди (не використовуються інші посилання).


Існує можливість використовувати оболонки, які підтримують змінну "OLDPWD" (більшість оболонок, подібних bourne: ksh, bash, zsh have $OLDPWD). Цікаво відзначити, що ksh не реалізує dirs, pod, pushd за замовчуванням (lksh, dash та інші також не мають popd, тому вони не використовуються тут):

popd && rmdir "$OLDPWD"

Або, більш ідіоматичний (той самий перелік снарядів, як і вище):

popd && rmdir ~-

Проблема в цьому, і те, що я намагався вирішити, полягає в тому, що ви не можете виконати rmdirпоточний каталог, де ви знаходитесь б, роблячи це. Ви повинні зробити це popdперед тим, як робити, rmdirале вам потрібно знати, що видалити, і popdце вам не скаже. Я, як і ви, думав, що dirs -l -1це відповідь, але з тих пір я виявив, що відповідь насправді потрібно використовувати $OLDPWD.
starfry

@starfry Я вважаю , що найкоротший відповідь на використання: popd && rmdir ~-.

@starfry Ви фактично можете видалити поточний каталог, без проблем, за умови, що він порожній (без примушування до стирання). Можна навіть зателефонувати rmdir "$PWD"добре. Також поточний каталог повинен бути тим, який створений mktmp -d(якщо він pushd $(mktemp -d)був виконаний заздалегідь) і до якого pushdпереміщується pwd. Так, так, після pushd і перед popd, створений каталог зберігається в $(dirs -1), я не бачу жодної проблеми.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.