4

I am using bash to get some information from audacious to conky for display (including album art). I get the path to the directory of the music file from audacious, then attempt to cd to that directory to find folder.jpg - if found, prepare for display. Unfortunately can't rely on 'well-formed' path names. None of them have a problem from terminal, but the evaluations that bash does... It chokes on doubled spaces ( ) ' / and probably others, although the current setup seems to handle - ok. Here's the relevant function:

GetArt ()
{
    file_path=`audtool --current-song-tuple-data file-path` # get the path to the song
    file_path=$(eval echo "${file_path}")                   # pre-expand to full path
    cd "${file_path}"
    if [[ ! -e "folder.jpg" ]];                             # if no art work found
    then
        cp ~/Work/vinyl.png /tmp/cover.png              # put in placeholder
    else
        convert "${file_path}""/folder.jpg" -resize 120x120 /tmp/cover.png # ready for showing
    fi
}

Any ideas, or would getting out the wire brush and removing the rust from my 'C' compiler be easier?

I tried with double quotes, single quotes, backticks, and even a construct like:

code=$code \"\$filename\""

but nothing seems to work correctly as yet. Fortunately it fails 'pretty' because it just pops up the "can't find art" substitute pic instead, but sometimes things burp all over stderr until the next song - or album.

1
  • you attribute file_path twice I guess the first line is useless? and the you eval it, eval is evil don't use it also why? why can't you just pass what the user passed as argument?
    – tatsu
    Jun 12, 2019 at 8:20

2 Answers 2

1

You can use file_path="${file_path/#~/$HOME}" to expand just the ~.

As far as I can tell, tilde expansion is the only kind of expansion that needs to be performed on the output of audtool --current-song-tuple-data file-path. Furthermore, only one specific case of tilde notation ever appears: if the path Audacious is using begins with the home directory of the user running Audacious, that part of the path is replaced with a ~ in the output of audtool. Since the audacious and audtool manpages don't clarify this, I've tried to get audtool to output paths prefixed with the ~username form of tilde expansion, and fortunately it appears never to do even that. I say "fortunately" because this means the situation is pretty simple.

Since the only transformation that needs to be done on the output of that audtool command is to replace a leading ~ with the path of the user's home directory, you can simply write code in your script that does that one expansion itself, and otherwise leaves the path unmodified. You've found that filenames of audio files you may play can contain characters treated specially by the shell, and if you play files named by other people it may even give rise to a security vulnerability in your script. By not having the shell execute--or even expand--arbitrary, untrusted text, you avoid that problem entirely.

There are several ways to expand ~ yourself. I suggest:

file_path="$(audtool --current-song-tuple-data file-path)"  # might need tilde expansion
file_path="${file_path/#~/$HOME}"  # do the tilde expansion, if needed

This manually performs the one transformation that may be needed. Specifically, when the output of audtool starts with a ~, that replaces it with the user's home directory, obtained by checking the value of the HOME variable. When the output doesn't start with a ~, no expansion is performed.

Note that this approach, ${file_path/#~/$HOME}, would not be correct if it were used as an attempt to simulate all forms of tilde expansion, because it would perform incorrect substitutions in the ~username form (and in some of the other, more obscure forms). However, so long as paths in the output of audtool only use tilde notation in the simple case of designating the current user's home directory--which I believe to be the case--then this approach is appropriate and correct for those paths.

The way it works is:

  • In general, Bash will expand ${parameter/pattern/string} into the value of parameter, but with the part that matches pattern replaced with string. If no part matches the pattern, the exact value of parameter is used.
  • When pattern is written with a leading #, it can only be matched starting at the very beginning of the value of parameter. (There are other characters besides # that are meaningful in this position: a % would require the pattern to be matched at the very end, and a / would cause the pattern to be matched and replaced as many times as it appears rather than at most once.)
  • ~ has no special meaning in a pattern and is therefore treated literally, as a character to match and replace.

See parameter expansion for details.

Note that the code I have written doesn't do anything to handle the case of audtool producing empty output, as happens when there is no song currently playing (or, as Simon Sudler mentioned, if there is an error). However, it doesn't break on empty output or get in the way of subsequent handling of it. So if you want to cover that case, you still can.

Finally, I should mention that I've glossed over two distinctions that I believe are unimportant to your use case:

  1. Since audtool's paths come from Audacious, if you were attempting the bizarre situation of running Audacious as one user and running audtool as another--and you somehow configured D-Bus so that worked--then you would have to care about ~ referring to a different user's home directory than the user running the script.
  2. Technically speaking, tilde expansion for the current user (which happens in a shell with ~ by itself or followed immediately by /) is not quite equivalent to "$HOME". Shells may attempt to handle the strange case where HOME is not set. Bash does attempt to handle this and still produce a correct expansion. I doubt you care about the distinction for this purpose, though. See tilde expansion for details.
0

The re-assignment of the file_path variables is not necessary. There are 2 signs that can happen, that might break the script:

  1. The variable is empty, because the audtool return an error
  2. The audtools returns a directory, which contains white-spaces.
  3. audtool includes ~ for the home directory

Here a proposal on howto address these issues:

GetArt ()
{
    file_path="$(audtool --current-song-tuple-data file-path)"
    file_path="$(readlink -f "$(bash -c "echo $file_path")")"
    if [[ ! -d $file_path ]]; then
        echo "error: $file_path does not exists"
        exit 1
    fi
    folder_jpg="$file_path/folder.jpg"
    if [[ ! -e "$folder_jpg" ]]; then
        cp ~/Work/vinyl.png /tmp/cover.png
    else
        convert "$folder_jpg" -resize 120x120 /tmp/cover.png
    fi
}

The "$(command)" handles the white-spaces in the folder name (if there are any). Don't use cd inside a script, if you don't need it. Checking if the return string is a directory is always a good idea. Working with the absolute path is in most cases better and prevents the execution of commands in the wrong folder.

Notes to the ~ expansion in bash:

The expansion of the ~ is handled by the bash, so readlink -f does not help here. So the variable containing the ~ must be executed un-quoted to be expanded (I updated the script). This can of course be handled in a single line...

# expansion from current bash
$ set -x; readlink -f ~; set +x
+ readlink -f /home/user
/home/user
+ set +x

# expansion will not work, with quoted ~
$ set -x; readlink -f '~'; set +x
+ readlink -f '~'
/home/user/~
+ set +x

# expansion from un-quoted ~ with sub-shell
$ set -x; readlink -f "$(bash -c "echo ~")"; set +x
++ bash -c 'echo ~'
+ readlink -f /home/user
/home/user
+ set +x
5
  • The readlink solution, if only providing expansion by where it is used, may be better for my use because the Music directory seen by Audacious is soft linked to /mnt/data2/Music. That would surely mean that the path would be more surely correct? I will try it out with some known problems. Both ideas avoid my use of cd - which appears to be the source of the trouble. Too much AmigaDOS experience, not enough Linux.
    – freebird54
    Jun 12, 2019 at 9:30
  • With the update to the second (readlink) solution, it appears to work very well, including /mnt/data2/Music in the path output. The First answer also worked for resolving the ~ problem that led to the whole mess! Thanks to both - it even handles some of the ugly ones! Thanks to Simon and @Eliah both. My up votes don't count yet, but they are there.
    – freebird54
    Jun 12, 2019 at 11:55
  • @freebird54 Running file_path="$(readlink -f "$(bash -c "echo $file_path")")", as this script does, is extremely dangerous unless all filenames are from trusted sources, which for music is often not the case. For example, with a file named ;touch whoa or $(touch whoa) (both valid filenames!) it runs the command touch whoa, creating a file called whoa. Any command that does not contain a / can be run in place of touch whoa. A maliciously crafted filename can control the system, destroy data, etc. It is also likely to fail by accident for filenames treated specially by the shell. Aug 27, 2019 at 22:14
  • @EliahKagan I totally agree on the security aspect. However if security is an issue, why use files from a untrusted source on your system? Aug 28, 2019 at 6:49
  • I am not really expecting a source like audacious music player to be crafting pathnames with security implications internally (I get the path from audtool). However - some of the hijinks I had to go through to make it work led to me cleaning 20 years of dust off Kernighan & Richie, and I rewrote the whole thing in 'C', which has far fewer problems! Sometimes I wish a simpler shell existed - call it kissh - that leaves expansions and the like to the person entering things. I miss the AmigaDOS shell the most - I loved the use of the list command to write a script for you!
    – freebird54
    Aug 29, 2019 at 6:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .