Могу ли я сделать cd локальным для функции?

Я думаю, это было бы чище и короче для вашего варианта использования:

<keybind key="XF86AudioLowerVolume">
 <action name="Execute">
  <command>sh -c "pactl list sinks | grep 'Sink #' | cut -d'#' -f2 | xargs -I@ pactl -- set-sink-volume @ -2000"</command>
 </action>
</keybind>
<keybind key="XF86AudioRaiseVolume">
 <action name="Execute">
  <command>sh -c "pactl list sinks | grep 'Sink #' | cut -d'#' -f2 | xargs -I@ pactl -- set-sink-volume @ +2000"</command>
 </action>
</keybind>

В нем будут перечислены все приемники и для каждого увеличения/уменьшения громкости. Например, если у вас есть разъем jack или динамики bluetooth, их будет несколько. Это именно та установка, которую я использую.

24
02.10.2020, 20:16
4 ответа

Вызовите его в подоболочке.

(doStuffAt some/path/)
2
18.03.2021, 23:00

Да. Просто заставьте функцию запускать свои команды в подоболочке ( )вместо групповой команды { }:

.
doStuffAt() (
        cd -- "$1" || exit # the subshell if cd failed.
        # do stuff
)

Круглые скобки(( ))открывают новую подоболочку, которая наследует среду своего родителя. Подоболочка завершится, как только команды, запускающие ее, будут выполнены, возвращая вас в родительскую оболочку, а cdповлияет только на подоболочку, поэтому ваш PWD останется неизменным.

Обратите внимание, что подоболочка также копирует все переменные оболочки, поэтому вы не можете передать информацию из функции подоболочки обратно в основной сценарий через глобальные переменные.

Подробнее о подоболочках см.man bash:

(list)

list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.

Сравните с:

{ list; }

list is simply executed in the current shell environment. list must be terminated with a newline or semicolon. This is known as a group command. The return status is the exit status of list. Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized. Since they do not cause a word break, they must be separated from list by whitespace or another shell metacharacter.

47
18.03.2021, 23:00

Это зависит.

Вы можете поместить функцию в подоболочку . (См. также Действительно ли скобки помещают команду в подоболочку?)То, что происходит в подоболочке, остается в подоболочке. Изменения, которые функция вносит в переменные, текущий каталог, перенаправления, ловушки и т. д., не влияют на вызывающий код. Подоболочка наследует все эти свойства от своего родителя, но в обратном направлении передачи нет. exitв подоболочке выходит только из подоболочки, а не из вызывающего процесса. Вы можете поместить фрагмент кода в подоболочку, заключив его в круглые скобки (разрывы строк и даже пробелы до и после круглых скобок необязательны):

(
  set -e # to exit the subshell as soon as an error happens
  cd -- "$1"
  do stuff # in $1
)
do more stuff # in whatever directory was current before the '('

Если вы хотите запустить всю функцию в подоболочке, вы можете использовать круглые скобки вместо фигурных скобок, чтобы обернуть код функции.

doStuffAt () (
    set -e
    cd -- "$1"
    # do stuff
)

При использовании синтаксиса определения функции в стиле Korn -вам потребуется:

function doStuffAt { (
    set -e
    cd -- "$1"
    # do stuff
) }

Недостатком подоболочки является то, что от нее ничего не ускользает. Если вам нужно изменить текущий каталог, но затем обновить некоторые переменные, вы не можете сделать это с помощью подоболочки. Есть только два простых способа получить информацию из подоболочки. Как и любая другая команда, подоболочка имеет статус выхода, но это целое число от 0 до 255, поэтому оно не передает много информации. Вы можете использовать подстановку команд для получения некоторого вывода :подстановка команд — это подоболочка, стандартный вывод которой (за вычетом завершающих символов новой строки )собирается в строку. Это позволяет вывести одну строку.

data=$(
  set -e
  cd -- "$1"
  do stuff # in $1
)
# Now you're still in the original directory, and you have some data in $data

Вы можете сохранить текущий каталог в переменную и восстановить его позже.

set -e
old_cwd="$PWD"
cd -- "$1"
…
cd "$old_cwd"

Однако это не очень надежно. Если код завершится между двумя командами cd, он окажется не в том каталоге. Если старая директория тем временем будет перемещена, вторая cdне вернется в нужное место.Вы можете находиться в каталоге, который у вас нет разрешения изменить на (, потому что у скрипта меньше привилегий, чем у его вызывающего ), и в этом случае второй cdзавершится ошибкой. Поэтому вам не следует этого делать, если только вы не находитесь в контролируемой среде, где это не может произойти (, например, для cdвхода и выхода из временного каталога, созданного вашим скриптом ).

Если вам необходимо временно изменить каталог и каким-либо образом повлиять на среду оболочки (, например, установить переменные ), вам необходимо тщательно разделить сценарии на части, влияющие на среду оболочки, и части, изменяющие текущую среду. каталог. Оболочка унаследовала ограничения ранних систем Unix, в которых не было возможности вернуться в предыдущий каталог. В современных unix-системах (вы можете «сохранить» файловый дескриптор текущего каталога и вернуться к нему с помощью fchdir()в обработчике исключений ), но интерфейс оболочки для этой функции отсутствует.

19
18.03.2021, 23:00

[этот ответ предполагает bash ; с другими оболочками работать не будет]

Использование подоболочки простое и совершенно нормальное в большинстве практических случаев, за исключением случаев, когда ваша функция должна изменять переменные в основном скрипте.

В этом случае вы можете использовать ловушку RETURNдля возврата к старому текущему каталогу после выхода из функции. Ловушка RETURNвызывается вне зависимости от того, как завершается ваша функция, и по умолчанию не наследуется другими функциями.

doStuffAt(){
    cd -- "$1" || return
    local opwd=$OLDPWD
    trap 'cd "$opwd"' RETURN
    #
    # do stuff
    shift; "$@"
    # change some variable
    wasAt+=("$PWD")
}


% doStuffAt /
% pwd
/home/user
% doStuffAt /usr
% echo ${wasAt[@]}
/ /usr

Но старый каталог мог быть переименован, когда вы пытаетесь вернуться к нему, и вы не сможете сделать это через его путь. В Linux вы можете эмулировать с помощью /dev/fdв оболочке более безопасный метод открытия файлового дескриптора в .и последующего вызова fchdirдля него:

doStuffAt() {
    local fd
    command exec {fd}<. || return
    trap 'cd -P "/dev/fd/$fd"; exec {fd}<&-' RETURN; exec {fd}<.
    cd -- "$1" || return

    # do stuff
    shift; "$@"
    # change some variable
    wasAt+=("$PWD")
}

Здесь {fd}<.используется с локальной переменной вместо фиксированного fd, чтобы функция была повторно -входной.

Обратите внимание, что cd -P /dev/fd/Xзавершится успешно, даже если ведущие каталоги из разрешенного пути недоступны. Простой пример:

t=$(mktemp -d); mkdir -p $t/b/c; exec 7<$t/b/c; chmod -rwx $t
cd -P /dev/fd/7  # this will succeed
pwd
cd $(pwd)        # this will fail

Конечно, старый cwd можно сделать недоступным сам, а не ведущие к нему каталоги (, удалив его xрежим ); но в этом случае fchdir(2)тоже не помогло бы.

Кроме того, все еще существует случай, когда старый cwd был удален . В этом случае ловушка может быть заменена наcd "/dev/fd/$fd"; cd -P. 2>/dev/null(-Pв основном для косметических целей ). Но неясно, как вообще будет полезен возврат к пустому удаленному каталогу.

7
18.03.2021, 23:00

Теги

Похожие вопросы