1
votes

I have a large list of urls (urls.txt) \n separated, all valid RFC https urls in the usual format of https://example.com/path/path/path/file.jpg

All urls seem to be TLS/SSL
All urls seem to be jpg or gif file All urls have manually been tested to work in Safari.

I want to read in the url's, loop through them, assign it to a variable as it passes through the loop, then I was thinking about using a heretic to shove in some applescript to run the open ( I am on mac OS X obviously :) ) command, which seems to nicely default url's to my default browser and also given a window with content in it, given my Safari preferences, a new tab will be fired open. So open as a raw command does just what I want.

For some reason, the second I step outside of bash into the heredoc, things get messy and stop working. Applescript is a strange little cousin of that really strange kid in school.

Here is what I have been trying:

#!/bin/bash

echo -e "Fire open a new tab for each url on a line in a file.\n"

urls=$(cat urls.txt)

for i in $urls; do
    echo "Opening URL item: $i"

#heredocs are nice
/usr/bin/osascript << EOF
set the_url to quoted form of $i
/usr/bin/open -a "/Applications/Safari.app" "$the_url"
EOF

done
exit 0

Here is a small snip of the error output that is sent back to the shell: $./go Fire open a new tab for each url on a line in a file.

Opening URL item: https://dl.dropboxusercontent.com/u/340087/drops/12.22.14/bbq-galore-and-more-59-082636.jpg 30:36: syntax error: A “:” can’t go after this identifier. (-2740) Opening URL item: https://dl.dropboxusercontent.com/u/340087/drops/12.22.14/card-88-082636.jpg 30:36: syntax error: A “:” can’t go after this identifier. (-2740) Opening URL item: https://dl.dropboxusercontent.com/u/340087/drops/12.22.14/catalog-c2-082636.jpg 30:36: syntax error: A “:” can’t go after this identifier. (-2740) Opening URL item: https://dl.dropboxusercontent.com/u/340087/drops/12.22.14/chris-360overlibrary-e9-082636.gif 30:36: syntax error: A “:” can’t go after this identifier. (-2740)

The colon can't go afer... error I am guessing is the colon in the url, since there aren't any others in the code that could cause the error in that location. I know there has to be a way to make it work, and I suspect it has to do with quoting. Perhaps I ned to add open Safari.app quoted form of $i or something like that.

Thank you for any help. Hopefully I can add to the top of the script echo "About to open $( wc -l urls.txt ) new tabs in Safari, are you ready?

2

2 Answers

3
votes

open is a Unix executable, just call it directly from your bash script - no need to get AppleScript involved. (In fact, you'd need to use do shell script in AppleScript to call it).

Here's a more robust version of your script, which, unfortunately, necessitates a clumsy workaround in order to work reliably with many URLs (as of OSX 10.10.1 / Safari 8.0.2):

The code should work both when targeting the default browser (open <url>) and when targeting Safari explicitly, as below (open -a Safari <url>).

#!/bin/bash

echo -e "Open a new tab for each url on a line in a file.\n"

# Read all URLs into bash _array_ variable `urls`.
IFS=$'\n' read -r -d '' -a urls < urls.txt

# Simple confirmation prompt.
echo "About to open ${#urls[@]} new tabs in Safari. Press Return to continue, ^C to abort."
read

# Open the URLs *one by one*, with a *sleep-and-retry* mechanism - see background
# below.     

timeout=3 # in seconds
for url in "${urls[@]}"; do # Loop over all URLs.
  SECONDS=0 # Start timing.
  while true; do # Keep trying to open the URL until success or timeout.
    # Omit '-a Safari' to open with the _default_ browser.
    # Add '-F' to start Safari without restoring previously open windows.
    # (If it's already open, the tabs will still be added.)
    # Without -F, the tabs are always added to the front window's 
    # existing tabs (which may stem from restoring a previous session).
    open -a Safari "$url" 2>/dev/null && break  # OK
    if (( SECONDS >= timeout )); then # Time out eventually.
      echo "WARNING: failed to open $url within $timeout second$( ((timeout == 1 )) || printf 's' )." >&2
      break # Proceed to next URL.
    fi
    # Sleep a little, then retry opening this URL.
    sleep 0.2
  done
done

Sadly, the following does NOT work reliably on OSX 10.10.1 / Safari 8.0.2, even though it SHOULD:

open -a "Safari" "${urls[@]}"  # pass all URLs at once.

Safari apparently gets too busy opening tabs so that later URLs often fail to open, resulting in nondescript LSOpenURLsWithRole() failed for the application /Applications/Safari.app with error -1712 for the URLs ... errors.
Given that open is by default asynchronous, there's no good reason for this to happen (Safari should just queue the requests and process them sequentially), so I've submitted a bug report at http://bugreport.apple.com - I encourage everyone to do the same.

The - clumsy - workaround is a one-by-one approach to opening, with a sleep-then-retry mechanism. Sadly, there's nothing in Safari's AppleScript dictionary that would allow us to check Safari's ready state.

Note, however, that even with this one-by-one approach, loading is asynchronous, so the tabs are NOT guaranteed to appear in the order they were specified.

Also, it seems that existing tabs with the same URL are sometimes reused, whereas a new tab is opened other times - couldn't find any rhyme or reason behind this.


The situation in AppleScript is similar, though not identical (assumes Safari 8.0.2 as the default browser):

  • Using just open location <url> without an application context (to target the default browser) partially fails too, and does so quietly.
    • e.g., with Safari initially not running and 16 input URLs, only 4 tabs end up open on my machine - no errors are reported
  • Curiously, using tell application Safari to open location <url> - i.e., explicitly targeting Safari - does seem to work properly.

Thus, the only way to directly avoid the timing issues as of Safari 8.0.2 is to explicitly target Safari via AppleScript - which may or may not be acceptable; here's a snippet that replaces the for loop in the code above:

open -a Safari
for url in "${urls[@]}"; do
  osascript -e "tell application \"Safari\" to open location \"$url\""
done
2
votes

Vanilla AppleScript:

set errorLog to {}
set urlText to read POSIX file "/Users/you/Desktop/urls.txt" as «class utf8»
set urlList to paragraphs of urlText
repeat with aUrl in urlList
    try
        open location aUrl
    on error
        set end of errorLog to aUrl
    end try
end repeat

EDIT

set myDelay to 0.2
set errorLog to {}
set urlText to read POSIX file "/Users/you/Desktop/urls.txt" as «class utf8»
set urlList to paragraphs of urlText
repeat with aUrl in urlList
    set aUrl to contents of aUrl
    if aUrl ≠ "" then
        try
            tell application "Safari" to open location aUrl
            -- open location aUrl
            delay myDelay
        on error
            set end of errorLog to aUrl
        end try
    end if
end repeat