6

I am trying to create a shell script for yt-dlp that takes an URL as input from the command line or if no input is given use an internal list. The script works when given input, but crashes when no input is given and thus using the internal list.

#!/usr/bin/env bash

URLfromTerminal="$1" PathToList="--batch-file '${HOME}/bin/ytdlp/Lists/Test.txt'"

[[ -z "$URLfromTerminal" ]] && Download="$PathToList" || Download="$URLfromTerminal"

yt-dlp -ciw
-S "res:1920"
--ffmpeg-location "$HOME"/bin/ffmpeg/ffmpeg
"$Download"

Error message when no input is given and using the internal list:

yt-dlp: error: no such option: --batch-file '/Users/UserName/bin/ytdlp/Lists/Test.txt'

Why is the path to the text file treated as an option by yt-dlp when expanded from a variable?

I am new to shell scripting and don't know what's best practice, so any general improvement is also welcome.

Kasam
  • 97

3 Answers3

4

Your issue is two-fold:

  1. You include literal quotes in the string supposedly holding the name of your file. Since the quotes are not part of the filename, they should not be included in the string.
  2. You include the option --batch-file in the string that holds the pathname of your file. This forces you to rely on the shell to correctly split the unquoted variable expansion. Since you quote the expansion of $Download, the shell won't split the string, and the whole string is used as a single argument.

#!/bin/sh

if [ -n "$1" ]; then printf '%s\n' "$1" else cat ~/bin/ytdlp/Lists/Test.txt fi | yt-dlp -ciw
-S res:1920
--ffmpeg-location ~/bin/ffmpeg/ffmpeg
-a -

This detects whether the first argument is empty or not using a simple -n test. If it is not empty, it is passed with printf to yt-dlp which will read it using -a - (the same as --batch-file -). If it is empty, your text file is passed to yt-dlp by means of cat.


Solving it in a manner more akin to what you are attempting:

#!/bin/sh

if [ -z "$1" ]; then set -- -a ~/bin/ytdlp/Lists/Test.txt fi

yt-dlp -ciw
-S res:1920
--ffmpeg-location ~/bin/ffmpeg/ffmpeg
"$@"

This passes the argument list of the script on to yt-dlp if $1 is non-empty. Otherwise, it sets the argument list to -a and your text file's pathname before using that in the call to yt-dlp.

This would allow you to pass any number of additional arguments on your script's command line, not just a single URL.

Kusalananda
  • 333,661
2

watch what you're quoting! You're trying to pass two argument strings, namely

--batch-file

and

'/Users/UserName/bin/ytdlp/Lists/Test.txt'

as one argument,

"--batch-file '/Users/UserName/bin/ytdlp/Lists/Test.txt'"

yt-dlp knows a parameter (i.e. it knows how to interpret an argument string) that is called --batch-file, just as it knows --help, for example, but it does not know --batch-file '/Users/UserName/bin/ytdlp/Lists/Test.txt'.

Simply don't have the " around the $Download on your last line.

terdon
  • 242,166
  • What if the path contains whitespace though? – terdon Dec 16 '22 at 12:38
  • Ah! Thanks! When reading your reply, that is fully logical. After removing the quotations around $Download yt-dlp gives another error message ERROR: batch file '/Users/UserName/bin/ytdlp/Lists/Test.txt' could not be read. But that file exists and has content. Is it something wrong with the single quotation marks here? – Kasam Dec 16 '22 at 12:39
  • yep, you're actually passing them as part of the file name. – Marcus Müller Dec 16 '22 at 13:03
1

It is generally safer and better to quote shell variables. However, in this case, it is the quotes that are breaking your script since "--batch-file '${HOME}/bin/ytdlp/Lists/Test.txt'" is being passed as a single argument and not as one flag and its value. The simplest solution that still allows you to work with arbitrary file names is to put only the path in the variable, not the option:

#!/usr/bin/env bash

URLfromTerminal="$1" PathToList="$HOME/bin/ytdlp/Lists/Test.txt"

if [[ -z "$URLfromTerminal" ]]; then DownloadFlag="--batch-file" DownloadPath="$PathToList" else DownloadPath="$URLfromTerminal" fi

yt-dlp -ciw
-S "res:1920"
--ffmpeg-location "$HOME"/bin/ffmpeg/ffmpeg
"$DownloadFlag" "$DownloadPath"

When the $DownloadFlag is empty, it will simply be passed as an empty string, so the command will still work.


The standard safe way of passing arguments with spaces is to use an array. So you might want to look into that option too because it will be necessary for more complex cases:

#!/usr/bin/env bash

URLfromTerminal="$1" PathToList="$HOME/bin/ytdlp/Lists/Test.txt"

if [[ -z "$URLfromTerminal" ]]; then DownloadPath=( "--batch-file" "$PathToList") else DownloadPath=( "$URLfromTerminal" ) fi

yt-dlp -ciw
-S "res:1920"
--ffmpeg-location "$HOME"/bin/ffmpeg/ffmpeg
"${DownloadPath[@]}"

terdon
  • 242,166