diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index b3047573..88298882 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -115,6 +115,11 @@ credits = [
'2010-2016 Jeremy Ashkenas, DocumentCloud',
'MIT',
'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'
+ ], [
+ 'Bash',
+ '2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.',
+ 'GFDL',
+ 'https://www.gnu.org/licenses/fdl-1.3.en.html'
], [
'Bluebird',
'2013-2017 Petka Antonov',
diff --git a/assets/stylesheets/application-dark.css.scss b/assets/stylesheets/application-dark.css.scss
index 4500f90f..e0d85477 100644
--- a/assets/stylesheets/application-dark.css.scss
+++ b/assets/stylesheets/application-dark.css.scss
@@ -35,6 +35,7 @@
'pages/angularjs',
'pages/apache',
'pages/async',
+ 'pages/bash',
'pages/bootstrap',
'pages/c',
'pages/cakephp',
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index f7321135..251d83e9 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -35,6 +35,7 @@
'pages/angularjs',
'pages/apache',
'pages/async',
+ 'pages/bash',
'pages/bootstrap',
'pages/c',
'pages/cakephp',
diff --git a/assets/stylesheets/pages/_bash.scss b/assets/stylesheets/pages/_bash.scss
new file mode 100644
index 00000000..c22d0493
--- /dev/null
+++ b/assets/stylesheets/pages/_bash.scss
@@ -0,0 +1,22 @@
+._bash {
+ // The page title is always the first element on the page, but not always the same type of element
+ // The exception is the homepage, where the links element comes first
+ > *:first-child:not(._links) {
+ @extend h1;
+ @extend %lined-heading;
+ }
+
+ dl > dt > code,
+ dl > dt > kbd {
+ @extend %note, %note-blue;
+ display: block;
+ padding: 1px 7px 2px 7px;
+ margin: 28px 0 14px 0;
+ font-weight: bold;
+ font-family: $baseFont;
+ }
+
+ th[align=left] {
+ border-left: 1px solid #d8d8d8;
+ }
+}
diff --git a/lib/docs/filters/bash/clean_html.rb b/lib/docs/filters/bash/clean_html.rb
new file mode 100644
index 00000000..d35bac63
--- /dev/null
+++ b/lib/docs/filters/bash/clean_html.rb
@@ -0,0 +1,63 @@
+module Docs
+ class Bash
+ class CleanHtmlFilter < Filter
+ def call
+ # Remove the navigation header and footer and the lines underneath and above it
+ at_css('.header + hr').remove
+ line_above = at_xpath('//div[@class="header"]/preceding::hr[1]')
+ line_above.remove unless line_above.nil?
+ css('.header').remove
+
+ # Remove chapter and section numbers from title
+ title_node = at_css('h1, h2, h3, h4, h5, h6')
+ title_node.content = title_node.content.gsub(/(\d+\.?)+/, '').strip
+
+ # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
+ title_node.content = title_node.content[3..-1] if title_node.content.start_with?("D. ")
+
+ # Remove columns containing a single space from tables
+ # In the original reference they are used to add width between two columns
+ xpath('//td[text()=" " and not(descendant::*)]').remove
+
+ # Add id's to additional entry nodes
+ css('dl > dt > code').each do |node|
+ # Only take the direct text (i.e. "
Hello World
" becomes "Hello")
+ node['id'] = node.xpath('text()').to_s.strip
+ end
+
+ # Fix hashes of index entries so they link to the correct hash on the linked page
+ css('table[class^=index-] td[valign=top] > a').each do |node|
+ path = node['href'].split('#')[0]
+ hash = node.content
+
+ # Fix the index entries linking to the Special Parameters page
+ # There are multiple index entries that should link to the same paragraph on that page
+ # Example: the documentation for "$!" is equal to the documentation for "!"
+ if path.downcase.include?('special-parameters')
+ if hash.size > 1 && hash[0] == '$'
+ hash = hash[1..-1]
+ end
+ end
+
+ node['href'] = path + '#' + hash
+ end
+
+ # Fix index table letter hashes (the "Jump to" hashes)
+ css('table[class^=index-] th > a').each do |node|
+ node['id'] = node['name']
+ end
+
+ # Remove the rows with a horizontal line in them from the index tables
+ css('td[colspan="4"]').remove
+
+ # Remove additional text from menu entry and index entry cells
+ css('td[valign=top]').each do |node|
+ link = node.at_css('a')
+ node.children = link unless link.nil?
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/bash/entries.rb b/lib/docs/filters/bash/entries.rb
new file mode 100644
index 00000000..3489020f
--- /dev/null
+++ b/lib/docs/filters/bash/entries.rb
@@ -0,0 +1,56 @@
+module Docs
+ class Bash
+ class EntriesFilter < Docs::EntriesFilter
+ def get_name
+ name = at_css('hr + a + *').content.gsub(/(\d+\.?)+/, '')
+
+ # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
+ name = name[3..-1] if name.start_with?('D. ')
+
+ name
+ end
+
+ def get_type
+ return 'Manual: Appendices' if name.start_with?('Appendix')
+ return 'Manual: Indexes' if at_css('a[rel=up]').content.include?("Index")
+ "Manual"
+ end
+
+ def additional_entries
+ entry_type = {
+ "Function Index" => "Functions",
+ "Index of Shell Builtin Commands" => "Builtin Commands",
+ "Index of Shell Reserved Words" => "Reserved Words",
+ "Parameter and Variable Index" => "Parameters and Variables"
+ }[name]
+
+ # Only extract additional entries from certain index pages
+ return [] if entry_type.nil?
+
+ entries = []
+
+ css('table[class^=index-] td[valign=top] > a').each_slice(2) do |entry_node, section_node|
+ entry_name = entry_node.content
+
+ page = section_node['href'].split('#')[0]
+ hash = entry_name
+
+ # The Special Parameters page has multiple additional entries which should link to the same paragraph
+ # Example: the documentation for "$!" is equal to the documentation for "!"
+ if page == 'special-parameters'
+ if hash.size > 1 && hash[0] == '$'
+ hash = hash[1..-1]
+ end
+ end
+
+ # Construct path to the page which the index links to
+ entry_path = '/html_node/' + page + '#' + hash
+
+ entries << [entry_name, entry_path, entry_type]
+ end
+
+ entries
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/bash.rb b/lib/docs/scrapers/bash.rb
new file mode 100644
index 00000000..feb0ddce
--- /dev/null
+++ b/lib/docs/scrapers/bash.rb
@@ -0,0 +1,21 @@
+module Docs
+ class Bash < UrlScraper
+ self.type = 'bash'
+ self.release = '4.4'
+ self.base_url = 'https://www.gnu.org/software/bash/manual'
+ self.root_path = '/html_node/index.html'
+ self.links = {
+ home: 'https://www.gnu.org/software/bash/',
+ code: 'http://git.savannah.gnu.org/cgit/bash.git'
+ }
+
+ html_filters.push 'bash/entries', 'bash/clean_html'
+
+ options[:only_patterns] = [/\/html_node\//]
+
+ options[:attribution] = <<-HTML
+ Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
+ Licensed under the GNU Free Documentation License.
+ HTML
+ end
+end
diff --git a/public/icons/docs/bash/16.png b/public/icons/docs/bash/16.png
new file mode 100644
index 00000000..2dbb2ebd
Binary files /dev/null and b/public/icons/docs/bash/16.png differ
diff --git a/public/icons/docs/bash/16@2x.png b/public/icons/docs/bash/16@2x.png
new file mode 100644
index 00000000..b9456a24
Binary files /dev/null and b/public/icons/docs/bash/16@2x.png differ
diff --git a/public/icons/docs/bash/SOURCE b/public/icons/docs/bash/SOURCE
new file mode 100644
index 00000000..1baa51d0
--- /dev/null
+++ b/public/icons/docs/bash/SOURCE
@@ -0,0 +1 @@
+https://github.com/odb/official-bash-logo