Portable check empty directory












3














With Bash and Dash, you can check for an empty directory using just the shell
(ignore dotfiles to keep things simple):



set *
if [ -e "$1" ]
then
echo 'not empty'
else
echo 'empty'
fi


However I recently learned that Zsh fails spectacularly in this case:



% set *
zsh: no matches found: *

% echo "$? $#"
1 0


So not only does the set command fail, but it doesn't even set $@. I suppose
I could test if $# is 0, but it appears that Zsh even stops execution:



% { set *; echo 2; }
zsh: no matches found: *


Compare with Bash and Dash:



$ { set *; echo 2; }
2


Can this be done in a way that works in bash, dash and zsh?










share|improve this question









New contributor




Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    3














    With Bash and Dash, you can check for an empty directory using just the shell
    (ignore dotfiles to keep things simple):



    set *
    if [ -e "$1" ]
    then
    echo 'not empty'
    else
    echo 'empty'
    fi


    However I recently learned that Zsh fails spectacularly in this case:



    % set *
    zsh: no matches found: *

    % echo "$? $#"
    1 0


    So not only does the set command fail, but it doesn't even set $@. I suppose
    I could test if $# is 0, but it appears that Zsh even stops execution:



    % { set *; echo 2; }
    zsh: no matches found: *


    Compare with Bash and Dash:



    $ { set *; echo 2; }
    2


    Can this be done in a way that works in bash, dash and zsh?










    share|improve this question









    New contributor




    Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      3












      3








      3







      With Bash and Dash, you can check for an empty directory using just the shell
      (ignore dotfiles to keep things simple):



      set *
      if [ -e "$1" ]
      then
      echo 'not empty'
      else
      echo 'empty'
      fi


      However I recently learned that Zsh fails spectacularly in this case:



      % set *
      zsh: no matches found: *

      % echo "$? $#"
      1 0


      So not only does the set command fail, but it doesn't even set $@. I suppose
      I could test if $# is 0, but it appears that Zsh even stops execution:



      % { set *; echo 2; }
      zsh: no matches found: *


      Compare with Bash and Dash:



      $ { set *; echo 2; }
      2


      Can this be done in a way that works in bash, dash and zsh?










      share|improve this question









      New contributor




      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      With Bash and Dash, you can check for an empty directory using just the shell
      (ignore dotfiles to keep things simple):



      set *
      if [ -e "$1" ]
      then
      echo 'not empty'
      else
      echo 'empty'
      fi


      However I recently learned that Zsh fails spectacularly in this case:



      % set *
      zsh: no matches found: *

      % echo "$? $#"
      1 0


      So not only does the set command fail, but it doesn't even set $@. I suppose
      I could test if $# is 0, but it appears that Zsh even stops execution:



      % { set *; echo 2; }
      zsh: no matches found: *


      Compare with Bash and Dash:



      $ { set *; echo 2; }
      2


      Can this be done in a way that works in bash, dash and zsh?







      bash zsh wildcards portability dash






      share|improve this question









      New contributor




      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question









      New contributor




      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question








      edited 50 mins ago









      terdon

      128k31252427




      128k31252427






      New contributor




      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 1 hour ago









      Three

      162




      162




      New contributor




      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      Three is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          2 Answers
          2






          active

          oldest

          votes


















          2














          While zsh's default behaviour is to give an error, this is controlled by the nomatch option. You can unset the option to leave the * in place the way that bash and dash do:



          setopt -o nonomatch


          While that command won't work in either of the others, you can just ignore that:



          setopt -o nonomatch 2>/dev/null || true ; set *


          This runs setopt on zsh, and suppresses the error output (2>/dev/null) and return code (|| true) of the failed command on the others.



          As written it's problematic if there is a file, for example, -e: then you will run set -e and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- * will be safer and prevent the option changes.






          share|improve this answer























          • If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
            – Three
            18 mins ago










          • @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
            – Michael Homer
            16 mins ago



















          2














          Most portable way would be via set and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:



          dir_empty(){ 
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }


          The way it works is by using set to set items in current working directory as positional parameters ( $1, $2, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal * as $1, . as $2 and $3, so we also account for hidden files here.



          Of course, it's a possibility that * could be a real filename, which is why we also perform [ -e "$1" ] check. If you want to use the function silently, we could rewrite it to return a value:



          dir_empty(){ 

          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          return 1
          else
          return 0
          fi
          }




          You can see it in action via this repl:



          main.sh



          rm -rf empty_dir
          mkdir empty_dir
          pwd
          cd empty_dir
          pwd
          dir_empty(){
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }
          dir_empty
          touch '*'
          dir_empty
          rm '*'
          touch '.dot_file'
          dir_empty


          Output:



          GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

          /home/runner
          /home/runner/empty_dir
          Directory is empty
          Directory not empty
          Directory not empty





          share|improve this answer

















          • 1




            This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
            – Three
            11 mins ago











          Your Answer








          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "106"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          Three is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492912%2fportable-check-empty-directory%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          2














          While zsh's default behaviour is to give an error, this is controlled by the nomatch option. You can unset the option to leave the * in place the way that bash and dash do:



          setopt -o nonomatch


          While that command won't work in either of the others, you can just ignore that:



          setopt -o nonomatch 2>/dev/null || true ; set *


          This runs setopt on zsh, and suppresses the error output (2>/dev/null) and return code (|| true) of the failed command on the others.



          As written it's problematic if there is a file, for example, -e: then you will run set -e and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- * will be safer and prevent the option changes.






          share|improve this answer























          • If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
            – Three
            18 mins ago










          • @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
            – Michael Homer
            16 mins ago
















          2














          While zsh's default behaviour is to give an error, this is controlled by the nomatch option. You can unset the option to leave the * in place the way that bash and dash do:



          setopt -o nonomatch


          While that command won't work in either of the others, you can just ignore that:



          setopt -o nonomatch 2>/dev/null || true ; set *


          This runs setopt on zsh, and suppresses the error output (2>/dev/null) and return code (|| true) of the failed command on the others.



          As written it's problematic if there is a file, for example, -e: then you will run set -e and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- * will be safer and prevent the option changes.






          share|improve this answer























          • If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
            – Three
            18 mins ago










          • @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
            – Michael Homer
            16 mins ago














          2












          2








          2






          While zsh's default behaviour is to give an error, this is controlled by the nomatch option. You can unset the option to leave the * in place the way that bash and dash do:



          setopt -o nonomatch


          While that command won't work in either of the others, you can just ignore that:



          setopt -o nonomatch 2>/dev/null || true ; set *


          This runs setopt on zsh, and suppresses the error output (2>/dev/null) and return code (|| true) of the failed command on the others.



          As written it's problematic if there is a file, for example, -e: then you will run set -e and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- * will be safer and prevent the option changes.






          share|improve this answer














          While zsh's default behaviour is to give an error, this is controlled by the nomatch option. You can unset the option to leave the * in place the way that bash and dash do:



          setopt -o nonomatch


          While that command won't work in either of the others, you can just ignore that:



          setopt -o nonomatch 2>/dev/null || true ; set *


          This runs setopt on zsh, and suppresses the error output (2>/dev/null) and return code (|| true) of the failed command on the others.



          As written it's problematic if there is a file, for example, -e: then you will run set -e and change the shell options to terminate whenever a command fails; there are worse outcomes if you're creative. set -- * will be safer and prevent the option changes.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 16 mins ago

























          answered 23 mins ago









          Michael Homer

          46.3k8121161




          46.3k8121161












          • If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
            – Three
            18 mins ago










          • @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
            – Michael Homer
            16 mins ago


















          • If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
            – Three
            18 mins ago










          • @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
            – Michael Homer
            16 mins ago
















          If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
          – Three
          18 mins ago




          If a file is named *, then $1 equals * and [ -e "$1" ] passes, meaning the example in the question still works
          – Three
          18 mins ago












          @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
          – Michael Homer
          16 mins ago




          @Three Fair point! It's conceptually iffy but it does actually work, since if there is a file there then... there is a file there.
          – Michael Homer
          16 mins ago













          2














          Most portable way would be via set and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:



          dir_empty(){ 
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }


          The way it works is by using set to set items in current working directory as positional parameters ( $1, $2, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal * as $1, . as $2 and $3, so we also account for hidden files here.



          Of course, it's a possibility that * could be a real filename, which is why we also perform [ -e "$1" ] check. If you want to use the function silently, we could rewrite it to return a value:



          dir_empty(){ 

          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          return 1
          else
          return 0
          fi
          }




          You can see it in action via this repl:



          main.sh



          rm -rf empty_dir
          mkdir empty_dir
          pwd
          cd empty_dir
          pwd
          dir_empty(){
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }
          dir_empty
          touch '*'
          dir_empty
          rm '*'
          touch '.dot_file'
          dir_empty


          Output:



          GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

          /home/runner
          /home/runner/empty_dir
          Directory is empty
          Directory not empty
          Directory not empty





          share|improve this answer

















          • 1




            This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
            – Three
            11 mins ago
















          2














          Most portable way would be via set and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:



          dir_empty(){ 
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }


          The way it works is by using set to set items in current working directory as positional parameters ( $1, $2, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal * as $1, . as $2 and $3, so we also account for hidden files here.



          Of course, it's a possibility that * could be a real filename, which is why we also perform [ -e "$1" ] check. If you want to use the function silently, we could rewrite it to return a value:



          dir_empty(){ 

          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          return 1
          else
          return 0
          fi
          }




          You can see it in action via this repl:



          main.sh



          rm -rf empty_dir
          mkdir empty_dir
          pwd
          cd empty_dir
          pwd
          dir_empty(){
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }
          dir_empty
          touch '*'
          dir_empty
          rm '*'
          touch '.dot_file'
          dir_empty


          Output:



          GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

          /home/runner
          /home/runner/empty_dir
          Directory is empty
          Directory not empty
          Directory not empty





          share|improve this answer

















          • 1




            This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
            – Three
            11 mins ago














          2












          2








          2






          Most portable way would be via set and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:



          dir_empty(){ 
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }


          The way it works is by using set to set items in current working directory as positional parameters ( $1, $2, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal * as $1, . as $2 and $3, so we also account for hidden files here.



          Of course, it's a possibility that * could be a real filename, which is why we also perform [ -e "$1" ] check. If you want to use the function silently, we could rewrite it to return a value:



          dir_empty(){ 

          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          return 1
          else
          return 0
          fi
          }




          You can see it in action via this repl:



          main.sh



          rm -rf empty_dir
          mkdir empty_dir
          pwd
          cd empty_dir
          pwd
          dir_empty(){
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }
          dir_empty
          touch '*'
          dir_empty
          rm '*'
          touch '.dot_file'
          dir_empty


          Output:



          GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

          /home/runner
          /home/runner/empty_dir
          Directory is empty
          Directory not empty
          Directory not empty





          share|improve this answer












          Most portable way would be via set and globstar for all POSIX-compliant shells. This has been shown in Gilles's answer on a related question. I've adapted the method slightly into a function:



          dir_empty(){ 
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }


          The way it works is by using set to set items in current working directory as positional parameters ( $1, $2, and so on ). If glob doesn't expand to any existing files, there shall only be one, literal * as $1, . as $2 and $3, so we also account for hidden files here.



          Of course, it's a possibility that * could be a real filename, which is why we also perform [ -e "$1" ] check. If you want to use the function silently, we could rewrite it to return a value:



          dir_empty(){ 

          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          return 1
          else
          return 0
          fi
          }




          You can see it in action via this repl:



          main.sh



          rm -rf empty_dir
          mkdir empty_dir
          pwd
          cd empty_dir
          pwd
          dir_empty(){
          # fully empty directory will only have
          # 3 parameters * . ..
          set -- * .*
          if [ $# -ne 3 ] || [ -e $1 ]; then
          echo "Directory not empty"
          else
          echo "Directory is empty"
          fi
          }
          dir_empty
          touch '*'
          dir_empty
          rm '*'
          touch '.dot_file'
          dir_empty


          Output:



          GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

          /home/runner
          /home/runner/empty_dir
          Directory is empty
          Directory not empty
          Directory not empty






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 16 mins ago









          Sergiy Kolodyazhnyy

          8,38212152




          8,38212152








          • 1




            This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
            – Three
            11 mins ago














          • 1




            This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
            – Three
            11 mins ago








          1




          1




          This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
          – Three
          11 mins ago




          This fails on Zsh for reasons detailed in the question - the function halts execution after the set command - so neither echo is ever reached
          – Three
          11 mins ago










          Three is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          Three is a new contributor. Be nice, and check out our Code of Conduct.













          Three is a new contributor. Be nice, and check out our Code of Conduct.












          Three is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Unix & Linux Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492912%2fportable-check-empty-directory%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          Refactoring coordinates for Minecraft Pi buildings written in Python