1

the code I have in my .zshrc is:

ytdcd () {  #youtube-dl that automatically puts stuff in a specific folder and returns to the former working directory after.
    cd ~/youtube/new/ && {
        youtube-dl "$@"
        cd - > /dev/null
    }
}
ytd() { #sofar, this function can only take one page. so, i can only send one youttube video code per line. will modify it to accept multiple lines..
    for i in $*;
        do
        params=" $params https://youtu.be/$i"
    done

    ytdcd -f 18 $params
}

so, on the commandline (terminal), when i enter ytd DFreHo3UCD0, i would like to have the video at https://youtu.be/DFreHo3UCD0 to be downloaded. the problem is that when I enter the command in succession, the system just tries to download the video for the previous command and rightly claims the download is complete.

For example, entering:

> ytd  DFreHo3UCD0
> ytd  L3my9luehfU

would not attempt to download the video for L3my9luehfU but only the video for DFreHo3UCD0 twice.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
nyxee
  • 2,773
  • 26
  • 22
  • bash and zsh are not compatible with each other. Do not cross-tag between shells; tag only for the shell you're actually using. – Charles Duffy Mar 14 '17 at 21:23
  • Actually, I'm surprised it's not incorrect for zsh too. Does `ytd` with multiple URLs actually work in practice? It looks like it should be passing a single string with all the URLs space-separated to `ytdcd`, not passing one argument per original argument. – Charles Duffy Mar 14 '17 at 21:32
  • (...followup: Testing has confirmed this; `(set -x; ytd one two three)` emits a final command of `ytdcd -f 18 ' https://youtu.be/one https://youtu.be/two https://youtu.be/three'`, passing all three URLs mushed together into *one* argument, not a separate argument per each). – Charles Duffy Mar 14 '17 at 21:41
  • my goal was to be able to send `ytd one two three` and end up with `ytdcd one && ytdcd two && ytdcd three`. I think the original luck of the solution provided by @jraynal below caused the hiccup. It should now be easilly modifiable to work for that case. – nyxee Mar 15 '17 at 01:03

2 Answers2

2

First -- there's no point to returning to the old directory for ytdcd: You can change to a new directory only inside a subshell, and then exec youtube-dl to replace that subshell with the application process:

This has fewer things to go wrong: Aborting the function's execution can't leave things in the wrong directory, because the parent shell (the one you're interactively using) never changed directories in the first place.

ytdcd () {
    (cd ~/youtube/new/ && exec youtube-dl "$@")
}

Second -- use an array when building argument lists, not a string.

If you use set -x to log its execution, you'll see that your original command runs something like:

ytdcd -f 18 'https://youtu.be/one https://youtu.be/two https://youtu.be/three'

See those quotes? That's because $params is a string, passed as a single argument, not an array. (In bash -- or another shell following POSIX rules -- an unquoted string expansion would be string-split and glob-expanded, but zsh doesn't follow POSIX rules).

The following builds up an array of separate arguments and passes them individually:

ytd() {
    local -a params=( )
    local i

    for i; do
        params+=( "https://youtu.be/$i" )
    done

    ytdcd -f 18 "${params[@]}"
}

Finally, it's come up that you don't actually intend to pass all the URLs to just one youtube-dl instance. To run a separate instance per URL, use:

ytd() {
    local i retval=0
    for i; do
        ytdcd -f 18 "$i" || retval=$?
    done
    return "$retval"
}

Note here that we're capturing non-success exit status, so as not to hide an error in any ytdcd instance other than the last (which would otherwise occur).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I will improve on this and see that calling `ytd one two three` downloads the three videos in succession, -- meaning it wall conveniently call `ytdcd one && ytdcd two && ytdcd three`. maybe you can provide the complete solution please. – nyxee Mar 15 '17 at 01:20
  • @nyxee, ...just to confirm the behavior in your comment -- you want to abort and *not* download `two` or `three` if `one` fails? – Charles Duffy Mar 15 '17 at 01:54
  • 1
    @nyxee, ...so, *if* `youtube-dl` accepts more than one URL on a command line (when passed as completely separate arguments), the above already covers it. If it doesn't, then you'll want to move `ytdcd` into the `for` loop, possibly with a `|| break` if you meant what you said about wanting the effect of `&&`. – Charles Duffy Mar 15 '17 at 01:55
  • no, if one fails, I would like to continue with the rest, or, maybe, to have both solutions and see whats best happens as time goes by. `youtube-dl` doesn't accept more than one URL (so-far), you have to pass each URL in a separate command. So, should I put ytdcd in the for loop, and have something like `ytdcom += (ytdcd -f 18 "https://youtu.be/$i")` then `done` then `ytdcom` – nyxee Mar 15 '17 at 05:25
  • 1
    `&&` explicitly says to stop on first failure. When you use it in examples, that's what you're asking for. – Charles Duffy Mar 15 '17 at 13:36
  • 1
    ...that said, I've amended the answer to be explicit about what I was suggesting. – Charles Duffy Mar 15 '17 at 13:38
  • the solutions are all useful. when i run into a command that can accept many arguments (strictly the URLs in this case), not the command (youtube-dl in this case) parameters, I'll be able to use the first `ytd()` construct in your solution. – nyxee Mar 15 '17 at 14:39
  • just to put icing on the cake, is it possible to put all the `ytdcd commands` in `separate threads` and run them in `parallel`? – nyxee Mar 15 '17 at 14:49
  • 1
    @nyxee, please don't code-format things that are technical terms rather than being actual code. But yes, you can absolutely put them in distinct processes running in parallel. Personally, I'd use GNU xargs for that: `printf '%s\0' "${params[@]}" | ( cd ~/youtube/new && exec xargs -P 8 -n 1 youtube-dl )` after building the `params` array as given in the answer that uses it; the above runs up to 8 at a time, though of course you can tune to taste. – Charles Duffy Mar 15 '17 at 15:01
1

I would declare param as local, so that you are not appending url after urls...

You can try to add this awesome function to your .zshrc:

funfun() {
        local _fun1="$_fun1 fun1!"
        _fun2="$_fun2 fun2!"

        echo "1 says: $_fun1"
        echo "2 says: $_fun2"
}

To observe the thing ;)

EDIT (Explanation):

When sourcing shell script, you add it to you current environment, that is why you can run those function you define. So, when those function use variables, by default, those variable will be global and accessible from anywhere in your environment! Therefore, In this case param is defined globally for all the length of your shell session. Since you want to allow the download of several video at once, you are appending values to this global variable, which will grow all the time.

Enforcing local tells zsh to limit the scope of params to the function only.

Another solution is to reset the variable when you call the function.

jraynal
  • 507
  • 3
  • 10
  • Looks right, I'm testing it now. can you please give a brief explanation of whats going on? – nyxee Mar 14 '17 at 20:22
  • Done, I hope this is helpful. I have to say that at first I was not sure what to think about this problem! I had to look into the doc of `youtube-dl`... and check those videos of yours :D – jraynal Mar 14 '17 at 20:31
  • whats the use of the curly braces here `${_fun1}`? I can see that removing them worked fine on MacOSX. – nyxee Mar 14 '17 at 21:11
  • No special reason, I like to put them to delimit the variable... http://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-in-variables-using-bash#8748880 – jraynal Mar 14 '17 at 21:21
  • Is there a reason you're flagging only `_fun1` but not `_fun2` local? – Charles Duffy Mar 14 '17 at 21:25
  • to demonstrate the effect of global versus local in this case. – jraynal Mar 14 '17 at 21:28
  • ...so, can you explain something here? How is the `$params` being split out into separate arguments to `ytdcd` if zsh doesn't string-split on unquoted expansion by default? (Which is to say -- it looks to me like there's a major bug in the OP's approach that this answer doesn't address). – Charles Duffy Mar 14 '17 at 21:33
  • Actually, in zsh, using `set -x` and then invoking the OP's code, even with the missing `local` declarations added, indicates that it still **does** have that problem. – Charles Duffy Mar 14 '17 at 21:36
  • I didn't look that deep... Since `youtube-dl` doesn't throw an error and do download the video (the wrong one), I didn't think about checking that. I just solve the problem mention by OP. But your solution (is better) and might help solve the long term objective to download several videos in one command line. – jraynal Mar 14 '17 at 21:46
  • Hmm. I wonder if maybe `youtube-dl` is iterating over `$*` instead of `"$@"` -- that would explain why it ignores the bug. – Charles Duffy Mar 14 '17 at 21:50
  • It requires python, so it might be a python script... Not sure it helps though. – jraynal Mar 14 '17 at 21:55
  • can you please explain the difference between using `$*` and `$@`. I saw the same results using both. – nyxee Mar 15 '17 at 01:18
  • @nyxee, ...so, the distinction varies between shells -- this is another place where zsh doesn't follow normal rules. However, in general, `$*` concatenates arguments into a string, whereas `"$@"` keeps them distinct. So, with a POSIX-compliant shell, `printf '%s\n' "$*"` will print exactly one line with all your arguments smooshed together, but `printf '%s\n' "$@"` will print one line per argument passed. – Charles Duffy Mar 15 '17 at 01:49
  • 1
    @nyxee, ...and by the way -- if you're the kind to figure out things by experimenting to see what works, let me urge you *not* to use zsh for that experimentation -- it lets you get away with lots of things that would behave badly elsewhere, so it's easy to get into habits that create bugs whenever you're working on code that's executed in a non-zsh shell. (Also, it's worth getting to know where corner cases tend to live -- whitespace-surrounded wildcards, literal newlines or quotes in filenames, etc -- and including those in your test cases). – Charles Duffy Mar 15 '17 at 01:52