Index: src/wp-includes/shortcodes.php
===================================================================
--- src/wp-includes/shortcodes.php	(revision 32800)
+++ src/wp-includes/shortcodes.php	(working copy)
@@ -305,7 +305,7 @@
  */
 function shortcode_parse_atts($text) {
 	$atts = array();
-	$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
+	$pattern = '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
 	$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
 	if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) {
 		foreach ($match as $m) {
Index: tests/phpunit/tests/shortcode.php
===================================================================
--- tests/phpunit/tests/shortcode.php	(revision 32800)
+++ tests/phpunit/tests/shortcode.php	(working copy)
@@ -119,6 +119,24 @@
 		$this->assertEquals( '[hyphen-foo-bar-baz]', do_shortcode( '[hyphen-foo-bar-baz]' ) );
 	}
 
+	/**
+	 * @ticket 9405
+	 */
+	function test_attr_hyphen() {
+		do_shortcode('[test-shortcode-tag foo="foo" foo-bar="foo-bar" foo-bar-="foo-bar-" -foo-bar="-foo-bar" -foo-bar-="-foo-bar-" foo-bar-baz="foo-bar-baz" -foo-bar-baz="-foo-bar-baz" foo--bar="foo--bar" /]');
+		$expected_attrs = array(
+			'foo' => 'foo',
+			'foo-bar' => 'foo-bar',
+			'foo-bar-' => 'foo-bar-',
+			'-foo-bar' => '-foo-bar',
+			'-foo-bar-' => '-foo-bar-',
+			'foo-bar-baz' => 'foo-bar-baz',
+			'-foo-bar-baz' => '-foo-bar-baz',
+			'foo--bar' => 'foo--bar',
+		);
+		$this->assertEquals( $expected_attrs, $this->atts );
+	}
+
 	function test_two_atts() {
 		do_shortcode('[test-shortcode-tag foo="asdf" bar="bing" /]');
 		$this->assertEquals( array('foo' => 'asdf', 'bar' => 'bing'), $this->atts );
