0

sometime ago I asked what was wrong about a bash script I was trying to do and I got a great solution: What's wrong with this youtube-dl automatic script?

I kept modifying the script to work with different youtube-dl command combinations and to deal with my very unstable Internet connection (that's the reason for the while/do loop) and it kept working flawlessly, but when I tried to use that same script structure to download Youtube playlists starting from a specific item in the list (e.g.: item number 15) that's when I get an error. I'm still pretty much a newbie in bash script (obviously), so I don't know what's wrong.

The script in question is this:

#!/bin/bash

function video {
youtube-dl --no-warnings -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' --socket-timeout 15 --hls-use-mpegts -R 64 --fragment-retries 64 --prefer-free-formats --all-subs --embed-subs -f 'bestvideo[height<=720]+bestaudio/best[height<=720]' "$@"
}

read -p "url: " url
video "$url"
while [ $? -ne 0 ]; do sleep 5 && video "$url" ; done
clear && echo completed!

So, for example, if I try to download a playlist, I just write in my Terminal: printf https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_ | list720

("list720" is the name of the script, of course) The script runs without problems and does exactly what I expect it to do.

But if I run in the Terminal: printf --playlist-start=15 https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_ | list720

I get the following error: bash: printf: --: invalid option printf: usage: printf [-v var] format [arguments] ERROR: '' is not a valid URL. Set --default-search "ytsearch" (or run youtube-dl "ytsearch:" ) to search YouTube

If I invert the order (1st the youtube URL and then the --playlist-start=15 command), the script downloads the whole playlist and omits the "--playlist-start" command.

I tried just running the youtube-dl command string directly in the terminal and added the "--playlist-start" and URL at the end and it runs perfectly: youtube-dl --no-warnings -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' --socket-timeout 15 --hls-use-mpegts -R 64 --fragment-retries 64 --prefer-free-formats --all-subs --embed-subs -f 'bestvideo[height<=720]+bestaudio/best[height<=720]' --playlist-start=15 https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_

...so I assume the problem is with the script.

Any help is welcome, thanks!

tuqueque
  • 31
  • 7
  • 1
    Presumably `--playlist-start=15` should be an option to your script, not to `printf` or something you want to print on the script's standard input. The syntax for that is `printf "$url" | list720 --playlist-start=15`. The stuff before `|` is an independent command which your script has no knowledge of. – tripleee Nov 27 '19 at 20:06
  • I tried your suggestion and it still doesn't work... e.g.: `printf https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_ | list720 --playlist-start=15` – tuqueque Nov 27 '19 at 21:09
  • You don't want to be using a URL as a `printf` format string. Also, URLs contain some characters that are interpreted by the shell. Try `printf "%s" 'https://...gdW0_'`. That is, make the URL an argument to the `%s` conversion specified, and put it into single quotes. If it contains single quotes, you can replace those by `'\''` (quote backslash quote quote). – Kaz Nov 28 '19 at 00:01

3 Answers3

2

A much better design is to accept any options and the URL as command-line arguments. Scripts which require interactive I/O are pesky to include in bigger scripts and generally harder to use (you lose the ability to use your shell's tab completion and command-line history etc).

#!/bin/bash

# Don't needlessly use Bash-only syntax for declaring a function
# Indent the code
video () {
  youtube-dl --no-warnings \
      -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' \
      --socket-timeout 15 --hls-use-mpegts -R 64 --fragment-retries 64 \
      --prefer-free-formats --all-subs --embed-subs \
      -f 'bestvideo[height<=720]+bestaudio/best[height<=720]' "$@"
}

until video "$@"; do
    sleep 5
done

Clearing the screen after finishing seems hostile so I took that out, too.

Now if you want to pass additional parameters to youtube-dl just include them as parameters to your script:

list720 --playlist-start=15 'https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_'

You should also usually quote any URLs in case they contain shell metacharacters. See also When to wrap quotes around a shell variable?

Notice how we always take care to use double quotes around "$@"; omitting them in this case is simply an error.

Notice also how inside the function, "$@" refers to the function's arguments, whereas in the main script, it refers to the script's command-line arguments.

Tangentially, using printf without a format string is problematic, too. If you pass in a string which contains a per-cent character, that will get interpreted as a format string.

bash$ printf 'http://example.com/%7Efnord'
http://example.com/0.000000E+00fnord

The proper solution is to always pass a format string as the first argument.

bash$ printf '%s\n' 'http://example.com/%7Efnord'
http://example.com/%7Efnord

But you don't need printf to pass something as standard input. Bash has "here strings":

list720 <<<'http://example.com/%7Efnord'

(This would of course only work with your old version which read the URL from standard input; the refactored script in this answer doesn't work that way.)

tripleee
  • 175,061
  • 34
  • 275
  • 318
0

SOLVED!

My brother (a "retired" programmer) took some time to evaluate how Bash script works and we figured a way of making the script work in a simpler way by just adding the youtube-dl commands and the Youtube URL as arguments.

The script changed a little bit, now it looks like this:

#!/bin/bash

function video() {
youtube-dl --no-warnings -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' --socket-timeout 15 --hls-use-mpegts -R 64 --fragment-retries 64 --prefer-free-formats --all-subs --embed-subs -f 'bestvideo[height<=720]+bestaudio/best[height<=720]' "$@"
}

video $@
while [ $? -ne 0 ]; do sleep 5 && video $@ ; done
clear && echo completed!

Now I just have to write in my terminal: list720 --playlist-start=15 https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_

And it works exactly as I want it to.

Thank you very much for your help and suggestions!

Community
  • 1
  • 1
tuqueque
  • 31
  • 7
  • 2
    Two suggestions: double-quote the references to `$@`, because sometimes it'll do weird things to unquoted variable/parameter references. Also, you can simplify the loop to `until video "$@"; do sleep 5; done`. Oh, and I recommend [shellcheck.net](https://www.shellcheck.net) for pointing out common mistakes like missing double-quotes. – Gordon Davisson Nov 28 '19 at 00:18
  • Thank you, @gordon... Now, I have another question (and again, sorry if the question is too "newbie"... I don't even know how to explain myself using the correct language): In your simplified loop suggestion, How does Bash "knows" that I want to re-call the video "$@" part after the "do sleep 5"? – tuqueque Nov 28 '19 at 01:07
  • 1
    That's how the `until` loop (and the very similar `while` loop) work. It runs the bit between `until` and `do`, and if that fails it does the part between `do` and `done`, then tries again (starting from the `until`) *until* it succeeds. (A `while` loop has the opposite test: it keeps running *while* the test part succeeds, i.e. until it fails.) What may be confusing you is that the test part isn't just a passive test, it's an actual activity (in fact, it's the activity you care about); but all tests are actually activities, this one's just a bit more active than most. – Gordon Davisson Nov 28 '19 at 01:44
  • Yes, I think I understand... I think!... If I understand correctly, I was using the "while [ $? -ne 0 ]" like an "until" and when I use the "until" I don't need to add that "[ $? -ne 0 ]" because that's what the "until" loop does in this case (more or less)... Anyway, thank you for your suggestions. I added the double quotes and I replaced the while with the until line and it works as expected! – tuqueque Nov 28 '19 at 02:35
  • For background on this, see https://stackoverflow.com/questions/36313216/why-is-testing-to-see-if-a-command-succeeded-or-not-an-anti-pattern – tripleee Nov 28 '19 at 05:18
-2

In bash the character '-' at the begin of a command is used to set an option, if you wants to print --playlist... you should use the escape character '\'.

Try something like printf "\-\-playlist..."

correction : printf '%s' '--playlist...'

gcadiou
  • 21
  • 4
  • 1
    The proper solution to that is `printf '%s' '--playlist'`. Probably the OP should generally use a format sting as the first argument if they insist on using `printf` (and learn to quote their strings). – tripleee Nov 27 '19 at 20:09
  • Sorry, but @gcadiou's suggestion didn't work: `printf \-\-playlist-start=15 https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_ | list720` Also, I might not be understanding @tripleee's suggestion, but that didn't work either: `printf 'https://www.youtube.com/playlist?list=PLS1QulWo1RIYmaxcEqw5JhK3b-6rgdWO_' '--playlist-start=15' | list720` – tuqueque Nov 27 '19 at 21:15
  • The first argument to `printf` is a format string. To print a string with dashes, `printf '%s' '--string-with-dashes'` and if you want to print other string too, add them after the format string (the format string is repeated until all arguments are exhausted). You still don't actually need or want to do this, as explained in my original comment to the question. – tripleee Nov 28 '19 at 05:23