Beets: Hook not producing expected behavior with pipes

Created on 10 Feb 2020  路  5Comments  路  Source: beetbox/beets

I'm trying to automate managing a converted library with hooks. When I update or remove an album in my main library (flac), I want to delete the converted version in ogg. For testing purposes, I am reimporting the same abum over and over to trigger the item_removed event.

This is my testing command, ran inside my flac directory so that beets reimports the album:

[cassie@forretress flac]$ beet -c ~/.config/beets/music.yaml -vv import "Tyler, the Creator - [2019] Igor (CD)/" -S https://musicbrainz.org/release/765263d3-4588-43ad-ba08-303b62b31e9e

This is one of the outputs of the hooks.

Sending event: item_removed
hook: running command "echo executing item_removed" for event item_removed
executing item_removed
hook: running command "dirname -- /mnt/raid/music/cassie/flac/Tyler, the Creator - [2019] Igor (CD)/01-01 Igor鈥檚 Theme.flac | echo" for event item_removed
/mnt/raid/music/cassie/flac/Tyler, the Creator - [2019] Igor (CD)
.
.

I can't figure out why there are two extra lines of .s. My initial attack was to simply call a script with an argument, but I can't figure out how to get the {item.path} to properly escape before being passed into the script.

Here is the relevant section of my configuration.

hook:
    hooks:
        - event: import
          command: "beet -c /home/cassie/.config/beets/music.yaml convert -a -y -d /mnt/raid/music/cassie/ogg"
        - event: item_removed
          command: "echo executing item_removed"
        - event: item_removed
          command: "dirname -- {item.path} | echo"

Here is my entire configuration file: https://0x0.st/i-qm.txt

needinfo

All 5 comments

Reading the dirname manpage: Output each NAME with its last non-slash component and trailing slashes removed; if NAME contains no /'s, output '.' (meaning the current directory).

I think my problem lies in not knowing how to properly escape {item.path}, and nothing else.

Hi! This is admittedly pretty confusing, but interpolated values should be "magically" escaped automatically. Here's what the algorithm does:

  1. Tokenize the string into pieces using "shell rules." So dirname -- {item.path} | echo becomes 5 tokens.
  2. Interpolate each token separately. (So we still have 5 tokens, but the third one has a path in it, which may contain spaces, and that's OK.)
  3. Execute the command without using a shell.

So what you're seeing is the output of dirname with three arguments: the path, the literal string |, and echo. Doing dirname on the latter two produces ..

So if you want to use proper shell constructs, you'll need to explicitly invoke sh. For example, something like this:

sh -c 'dirname $@ | echo' {item.path}

This is exactly what I needed. Thank you

Here's my eventual solution, for posterity.

music.yaml

hook:
    hooks:
        - event: import
          command: "beet -c /home/cassie/.config/beets/music.yaml convert -a -y -d /mnt/raid/music/cassie/ogg"
        - event: item_removed
          command: "sh -c '/home/cassie/.config/beets/remove_item.sh $0' {item.path}"

remove_item.sh

#!/bin/bash

path="$@"

parent="$(dirname -- "$path")"
parent=$(printf '%s\n' "${parent}" | sed -e 's/flac/ogg/')
rm -vr "$parent" 2> /dev/null

exit 0

Awesome!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Moonbase59 picture Moonbase59  路  4Comments

foways picture foways  路  5Comments

ItsKonix picture ItsKonix  路  4Comments

Freso picture Freso  路  4Comments

ctrueden picture ctrueden  路  3Comments