67

I want to extract the URL from within the anchor tags of an html file. This needs to be done in BASH using SED/AWK. No perl please.

What is the easiest way to do this?

casperOne
  • 73,706
  • 19
  • 184
  • 253
codaddict
  • 445,704
  • 82
  • 492
  • 529
  • 9
    Read this and be enlightened: http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 – Dennis Williamson Dec 10 '09 at 14:44
  • 1
    If you don't mind that: *There is no guarantee that you find all urls.* **or** *There is no guarantee that all urls you find are valid.* use one of the examples below. If you do mind use an appropriate tool for the job (perl, python, ruby) – Nifle Dec 10 '09 at 14:59
  • My previous comment is of course for any *easy* solution you might try. awk is powerful enough to do the job, heck you could theoretically implement perl in awk... – Nifle Dec 10 '09 at 15:02
  • 8
    Is this like one of those survivor challenges, where you have to live for three days eating only termites? If not, seriously, why the restriction? Every modern system can install at least Perl, and from there, you have the whole web – Randal Schwartz Dec 21 '09 at 02:33

16 Answers16

65

You could also do something like this (provided you have lynx installed)...

Lynx versions < 2.8.8

lynx -dump -listonly my.html

Lynx versions >= 2.8.8 (courtesy of @condit)

lynx -dump -hiddenlinks=listonly my.html
fatuhoku
  • 4,815
  • 3
  • 30
  • 70
Hardy
  • 18,659
  • 3
  • 49
  • 65
  • 4
    In Lynx 2.8.8 this has become `lynx -dump -hiddenlinks=listonly my.html` – condit May 07 '14 at 22:17
  • Better `lynx dump -listonly -hiddenlinks=listonly my.html`; if you don't still have the bare `-listonly` you get body text, not just links. – Charles Duffy Sep 25 '22 at 17:33
45

You asked for it:

$ wget -O - http://stackoverflow.com | \
  grep -io '<a href=['"'"'"][^"'"'"']*['"'"'"]' | \
  sed -e 's/^<a href=["'"'"']//i' -e 's/["'"'"']$//i'

This is a crude tool, so all the usual warnings about attempting to parse HTML with regular expressions apply.

Hayden Schiff
  • 3,280
  • 19
  • 41
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
18
grep "<a href=" sourcepage.html
  |sed "s/<a href/\\n<a href/g" 
  |sed 's/\"/\"><\/a>\n/2'
  |grep href
  |sort |uniq
  1. The first grep looks for lines containing urls. You can add more elements after if you want to look only on local pages, so no http, but relative path.
  2. The first sed will add a newline in front of each a href url tag with the \n
  3. The second sed will shorten each url after the 2nd " in the line by replacing it with the /a tag with a newline Both seds will give you each url on a single line, but there is garbage, so
  4. The 2nd grep href cleans the mess up
  5. The sort and uniq will give you one instance of each existing url present in the sourcepage.html
kerkael
  • 181
  • 1
  • 2
16

With the Xidel - HTML/XML data extraction tool, this can be done via:

$ xidel --extract "//a/@href" http://example.com/

With conversion to absolute URLs:

$ xidel --extract "//a/resolve-uri(@href, base-uri())" http://example.com/
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • concat expects 2 arguments but here only one (base url is given). err:XPST0017: unknown function: concat #1 Did you mean: In module http://www.w3.org/2005/xpath-functions: concat #2-65535 – smihael Aug 24 '17 at 08:04
  • @smihael: You're right, that's superfluous here. Removed it. Thanks for noticing! – Ingo Karkat Aug 24 '17 at 08:13
15

I made a few changes to Greg Bacon Solution

cat index.html | grep -o '<a .*href=.*>' | sed -e 's/<a /\n<a /g' | sed -e 's/<a .*href=['"'"'"]//' -e 's/["'"'"'].*$//' -e '/^$/ d'

This fixes two problems:

  1. We are matching cases where the anchor doesn't start with href as first attribute
  2. We are covering the possibility of having several anchors in the same line
Crisboot
  • 1,420
  • 2
  • 18
  • 29
12

An example, since you didn't provide any sample

awk 'BEGIN{
RS="</a>"
IGNORECASE=1
}
{
  for(o=1;o<=NF;o++){
    if ( $o ~ /href/){
      gsub(/.*href=\042/,"",$o)
      gsub(/\042.*/,"",$o)
      print $(o)
    }
  }
}' index.html
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
5

You can do it quite easily with the following regex, which is quite good at finding URLs:

\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))

I took it from John Gruber's article on how to find URLs in text.

That lets you find all URLs in a file f.html as follows:

cat f.html | grep -o \
    -E '\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'
nes1983
  • 15,209
  • 4
  • 44
  • 64
  • 2
    complicated, and fails when href is like this: ... HREF="http://www.somewhere.com/" ADD_DATE="1197958879" LAST_MODIFIED="1249591429"> ... – ghostdog74 Dec 10 '09 at 14:35
  • I tried it on the daringfireball page itself and it found all links. other solutions may fail because href= could be somewhere inside regular text. it's difficult to get this absolutely right without parsing the HTML according to its grammar. – nes1983 Dec 10 '09 at 14:45
  • 5
    You don't need to have a cat before the grep. Just put f.html at the end of grep – monksy Apr 13 '12 at 05:10
  • And grep -o can fail due to a bug in some versions of grep. – kisp Aug 23 '13 at 21:45
5

I am assuming you want to extract a URL from some HTML text, and not parse HTML (as one of the comments suggests). Believe it or not, someone has already done this.

OT: The sed website has a lot of good information and many interesting/crazy sed scripts. You can even play Sokoban in sed!

Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
  • This is the easiest and simplest answer. Just do e.g. `wget http://sed.sourceforge.net/grabbag/scripts/list_urls.sed -O ~/bin/list_urls.sed && chmod +x ~/bin/list_urls.sed` to get the script, and then `wget http://www.example.com -O - | ~/bin/list_urls.sed > example.com.urls.txt` to get the urls in a text file! – arjan Feb 18 '16 at 22:56
4

In bash, the following should work. Note that it doesn't use sed or awk, but uses tr and grep, both very standard and not perl ;-)

$ cat source_file.html | tr '"' '\n' | tr "'" '\n' | grep -e '^https://' -e '^http://' -e'^//' | sort | uniq

for example:

$ curl "https://www.cnn.com" | tr '"' '\n' | tr "'" '\n' | grep -e '^https://' -e '^http://' -e'^//' | sort | uniq

generates

//s3.amazonaws.com/cnn-sponsored-content
//twitter.com/cnn
https://us.cnn.com
https://www.cnn.com
https://www.cnn.com/2018/10/27/us/new-york-hudson-river-bodies-identified/index.html\
https://www.cnn.com/2018/11/01/tech/google-employee-walkout-andy-rubin/index.html\
https://www.cnn.com/election/2016/results/exit-polls\
https://www.cnn.com/profiles/frederik-pleitgen\
https://www.facebook.com/cnn
etc...
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
3

This is my first post, so I try to do my best explaining why I post this answer...

  1. Since the first 7 most voted answers, 4 include GREP even when the post explicitly says "using sed or awk only".
  2. Even when the post requires "No perl please", due to the previous point, and because use PERL regex inside grep.
  3. and because this is the simplest way ( as far I know , and was required ) to do it in BASH.

So here come the simplest script from GNU grep 2.28:

grep -Po 'href="\K.*?(?=")'

About the \K switch , not info was founded in MAN and INFO pages, so I came here for the answer.... the \K switch get rid the previous chars ( and the key itself ). Bear in mind following the advice from man pages: "This is highly experimental and grep -P may warn of unimplemented features."

Of course, you can modify the script to meet your tastes or needs, but I found it pretty straight for what was requested in the post , and also for many of us...

I hope folks you find it very useful.

thanks!!!

Community
  • 1
  • 1
X00D45
  • 61
  • 3
2

Expanding on kerkael's answer:

grep "<a href=" sourcepage.html
  |sed "s/<a href/\\n<a href/g" 
  |sed 's/\"/\"><\/a>\n/2'
  |grep href
  |sort |uniq
# now adding some more
  |grep -v "<a href=\"#"
  |grep -v "<a href=\"../"
  |grep -v "<a href=\"http"

The first grep I added removes links to local bookmarks.

The second removes relative links to upper levels.

The third removes links that don't start with http.

Pick and choose which one of these you use as per your specific requirements.

Community
  • 1
  • 1
Nikhil VJ
  • 5,630
  • 7
  • 34
  • 55
1

Go over with a first pass replacing the start of the urls (http) with a newline (\nhttp). Then you have guaranteed for yourself that your link starts at the beginning of the line and is the only URL on the line.

The rest should be easy, here is an example:

sed "s/http/\nhttp/g" <(curl "http://www.cnn.com") | sed -n "s/\(^http[s]*:[a-Z0-9/.=?_-]*\)\(.*\)/\1/p"

alias lsurls='_(){ sed "s/http/\nhttp/g" "${1}" | sed -n "s/\(^http[s]*:[a-Z0-9/.=?_-]*\)\(.*\)/\1/p"; }; _'

1

Eschewing the awk/sed requirement:

  1. urlextract is made just for such a task (documentation).
  2. urlview is an interactive CLI solution (github repo).
  • [urlextract](https://github.com/lipoja/URLExtract/) worked fantastic — I was able to only extract around 30% of the desired URLs (exactly 100 in total) with lynx and grep. lynx gives the `Bad HTML!` error for the page (or a local HTML file in this case.' – user598527 Aug 06 '23 at 15:40
0

You can try:

curl --silent -u "<username>:<password>" http://<NAGIOS_HOST/nagios/cgi-bin/status.cgi|grep 'extinfo.cgi?type=1&host='|grep "status"|awk -F'</A>' '{print $1}'|awk -F"'>" '{print $3"\t"$1}'|sed 's/<\/a>&nbsp;<\/td>//g'| column -c2 -t|awk '{print $1}'
Anthon
  • 69,918
  • 32
  • 186
  • 246
dpathak
  • 29
  • 1
0

That's how I tried it for better view, create shell file and give link as parameter, it will create temp2.txt file.

a=$1

lynx -listonly -dump "$a" > temp

awk 'FNR > 2 {print$2}' temp > temp2.txt

rm temp

>sh test.sh http://link.com
Abhishek Gurjar
  • 7,426
  • 10
  • 37
  • 45
0

I scrape websites using Bash exclusively to verify the http status of client links and report back to them on errors found. I've found awk and sed to be the fastest and easiest to understand. Props to the OP.

curl -Lk https://example.com/ | sed -r 's~(href="|src=")([^"]+).*~\n\1\2~g' | awk '/^(href|src)/,//'

Because sed works on a single line, this will ensure that all urls are formatted properly on a new line, including any relative urls. The first sed finds all href and src attributes and puts each on a new line while simultaneously removing the rest of the line, inlcuding the closing double qoute (") at the end of the link.

Notice I'm using a tilde (~) in sed as the defining separator for substitution. This is preferred over a forward slash (/). The forward slash can confuse the sed substitution when working with html.

The awk finds any line that begins with href or src and outputs it.

Once the content is properly formatted, awk or sed can be used to collect any subset of these links. For example, you may not want base64 images, instead you want all the other images. Our new code would look like:

curl -Lk https://example.com/ | sed -r 's~(href="|src=")([^"]+).*~\n\1\2~g' | awk '/^(href|src)/,//' | awk '/^src="[^d]/,//'

Once the subset is extracted, just remove the href=" or src="

sed -r 's~(href="|src=")~~g'

This method is extremely fast and I use these in Bash functions to format the results across thousands of scraped pages for clients that want someone to review their entire site in one scrape.

CodeMilitant
  • 140
  • 8