@@ -1372,13 +1372,14 @@ async fn glob_basic() {
13721372 . run ( )
13731373 . await ;
13741374
1375+ // ** only matches recursively when globstar is enabled
13751376 TestBuilder :: new ( )
13761377 . directory ( "sub_dir/sub" )
13771378 . file ( "sub_dir/sub/1.txt" , "1\n " )
13781379 . file ( "sub_dir/2.txt" , "2\n " )
13791380 . file ( "sub_dir/other.ts" , "other\n " )
13801381 . file ( "3.txt" , "3\n " )
1381- . command ( "cat **/*.txt" )
1382+ . command ( "shopt -s globstar && cat **/*.txt" )
13821383 . assert_stdout ( "3\n 2\n 1\n " )
13831384 . run ( )
13841385 . await ;
@@ -1389,7 +1390,7 @@ async fn glob_basic() {
13891390 . file ( "sub_dir/2.txt" , "2\n " )
13901391 . file ( "sub_dir/other.ts" , "other\n " )
13911392 . file ( "3.txt" , "3\n " )
1392- . command ( "cat $PWD/**/*.txt" )
1393+ . command ( "shopt -s globstar && cat $PWD/**/*.txt" )
13931394 . assert_stdout ( "3\n 2\n 1\n " )
13941395 . run ( )
13951396 . await ;
@@ -1702,10 +1703,10 @@ async fn sigpipe_from_pipeline() {
17021703
17031704#[ tokio:: test]
17041705async fn shopt ( ) {
1705- // query all options (default: failglob on, nullglob off)
1706+ // query all options (default: failglob on, globstar off, nullglob off)
17061707 TestBuilder :: new ( )
17071708 . command ( "shopt" )
1708- . assert_stdout ( "failglob\t on\n nullglob\t off\n " )
1709+ . assert_stdout ( "failglob\t on\n globstar \t off \ n nullglob\t off\n " )
17091710 . run ( )
17101711 . await ;
17111712
@@ -1767,7 +1768,23 @@ async fn shopt() {
17671768 // multiple options
17681769 TestBuilder :: new ( )
17691770 . command ( "shopt -s nullglob && shopt -u failglob && shopt" )
1770- . assert_stdout ( "failglob\t off\n nullglob\t on\n " )
1771+ . assert_stdout ( "failglob\t off\n globstar\t off\n nullglob\t on\n " )
1772+ . run ( )
1773+ . await ;
1774+
1775+ // query globstar option
1776+ TestBuilder :: new ( )
1777+ . command ( "shopt globstar" )
1778+ . assert_stdout ( "globstar\t off\n " )
1779+ . assert_exit_code ( 1 ) // returns 1 when option is off
1780+ . run ( )
1781+ . await ;
1782+
1783+ // enable globstar
1784+ TestBuilder :: new ( )
1785+ . command ( "shopt -s globstar && shopt globstar" )
1786+ . assert_stdout ( "globstar\t on\n " )
1787+ . assert_exit_code ( 0 )
17711788 . run ( )
17721789 . await ;
17731790}
@@ -1850,6 +1867,73 @@ async fn shopt_failglob() {
18501867 . await ;
18511868}
18521869
1870+ #[ tokio:: test]
1871+ async fn shopt_globstar ( ) {
1872+ // without globstar: ** behaves like * (single-segment match, doesn't recurse)
1873+ // **/*.txt without globstar is like */*.txt - matches one level deep only
1874+ TestBuilder :: new ( )
1875+ . directory ( "sub/deep" )
1876+ . file ( "a.txt" , "a\n " )
1877+ . file ( "sub/b.txt" , "b\n " )
1878+ . file ( "sub/deep/c.txt" , "c\n " )
1879+ . command ( "echo **/*.txt" )
1880+ . assert_stdout ( & format ! ( "sub{FOLDER_SEPERATOR}b.txt\n " ) ) // only matches one level deep
1881+ . assert_exit_code ( 0 )
1882+ . run ( )
1883+ . await ;
1884+
1885+ // with globstar: ** matches zero or more directories
1886+ TestBuilder :: new ( )
1887+ . directory ( "sub/deep" )
1888+ . file ( "a.txt" , "a\n " )
1889+ . file ( "sub/b.txt" , "b\n " )
1890+ . file ( "sub/deep/c.txt" , "c\n " )
1891+ . command ( "shopt -s globstar && echo **/*.txt | tr ' ' '\\ n' | sort" )
1892+ . assert_stdout ( & format ! (
1893+ "a.txt\n sub{FOLDER_SEPERATOR}b.txt\n sub{FOLDER_SEPERATOR}deep{FOLDER_SEPERATOR}c.txt\n "
1894+ ) )
1895+ . assert_exit_code ( 0 )
1896+ . run ( )
1897+ . await ;
1898+
1899+ // globstar with **/ prefix matches recursively in subdirectories
1900+ TestBuilder :: new ( )
1901+ . directory ( "sub/deep" )
1902+ . file ( "root.txt" , "r\n " )
1903+ . file ( "sub/nested.txt" , "n\n " )
1904+ . file ( "sub/deep/deeper.txt" , "d\n " )
1905+ . command ( "shopt -s globstar && echo **/*.txt | tr ' ' '\\ n' | sort" )
1906+ . assert_stdout ( & format ! (
1907+ "root.txt\n sub{FOLDER_SEPERATOR}deep{FOLDER_SEPERATOR}deeper.txt\n sub{FOLDER_SEPERATOR}nested.txt\n "
1908+ ) )
1909+ . assert_exit_code ( 0 )
1910+ . run ( )
1911+ . await ;
1912+
1913+ // single * still behaves normally even with globstar enabled
1914+ TestBuilder :: new ( )
1915+ . directory ( "sub" )
1916+ . file ( "a.txt" , "a\n " )
1917+ . file ( "sub/b.txt" , "b\n " )
1918+ . command ( "shopt -s globstar && echo *.txt" )
1919+ . assert_stdout ( "a.txt\n " )
1920+ . assert_exit_code ( 0 )
1921+ . run ( )
1922+ . await ;
1923+
1924+ // disabling globstar goes back to single-segment behavior
1925+ TestBuilder :: new ( )
1926+ . directory ( "sub/deep" )
1927+ . file ( "a.txt" , "a\n " )
1928+ . file ( "sub/b.txt" , "b\n " )
1929+ . file ( "sub/deep/c.txt" , "c\n " )
1930+ . command ( "shopt -s globstar && shopt -u globstar && echo **/*.txt" )
1931+ . assert_stdout ( & format ! ( "sub{FOLDER_SEPERATOR}b.txt\n " ) ) // back to single-level match
1932+ . assert_exit_code ( 0 )
1933+ . run ( )
1934+ . await ;
1935+ }
1936+
18531937#[ tokio:: test]
18541938async fn pipefail_option ( ) {
18551939 // Without pipefail: exit code is from last command (0)
0 commit comments