How can I check if a file can be created or truncated/overwritten in bash?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
3
down vote

favorite
1












The user calls my script with a file path that will be either be created or overwritten at some point in the script, like foo.sh file.txt or foo.sh dir/file.txt.



The create-or-overwrite behavior is much like the requirements for putting the file on the right side of the > output redirect operator, or passing it as an argument to tee (in fact, passing it as an argument to tee is exactly what I'm doing).



Before I get into the guts of the script, I want to make a reasonable check if the file can be created/overwritten, but not actually create it. This check doesn't have to be perfect, and yes I realize that the situation can change between the check and the point where the file is actually written - but here I'm OK with a best effort type solution so I can bail out early in the case that the file path is invalid.



Examples of reasons the file couldn't created:



  • the file contains a directory component, like dir/file.txt but the directory dir doesn't exist

  • the user doens't have write permissions in the specified directory (or the CWD if no directory was specified

Yes, I realize that checking permissions "up front" is no the UNIX way, rather I should just try the operation and ask forgiveness later. In my particular script however, this leads to a bad user experience and I can't change the responsible component.










share|improve this question























  • Will the argument always be based on the current directory or could the user specify a full path?
    – Jesse_b
    3 hours ago










  • @Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
    – BeeOnRope
    3 hours ago










  • @BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
    – Stéphane Chazelas
    2 hours ago










  • @StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
    – BeeOnRope
    2 hours ago














up vote
3
down vote

favorite
1












The user calls my script with a file path that will be either be created or overwritten at some point in the script, like foo.sh file.txt or foo.sh dir/file.txt.



The create-or-overwrite behavior is much like the requirements for putting the file on the right side of the > output redirect operator, or passing it as an argument to tee (in fact, passing it as an argument to tee is exactly what I'm doing).



Before I get into the guts of the script, I want to make a reasonable check if the file can be created/overwritten, but not actually create it. This check doesn't have to be perfect, and yes I realize that the situation can change between the check and the point where the file is actually written - but here I'm OK with a best effort type solution so I can bail out early in the case that the file path is invalid.



Examples of reasons the file couldn't created:



  • the file contains a directory component, like dir/file.txt but the directory dir doesn't exist

  • the user doens't have write permissions in the specified directory (or the CWD if no directory was specified

Yes, I realize that checking permissions "up front" is no the UNIX way, rather I should just try the operation and ask forgiveness later. In my particular script however, this leads to a bad user experience and I can't change the responsible component.










share|improve this question























  • Will the argument always be based on the current directory or could the user specify a full path?
    – Jesse_b
    3 hours ago










  • @Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
    – BeeOnRope
    3 hours ago










  • @BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
    – Stéphane Chazelas
    2 hours ago










  • @StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
    – BeeOnRope
    2 hours ago












up vote
3
down vote

favorite
1









up vote
3
down vote

favorite
1






1





The user calls my script with a file path that will be either be created or overwritten at some point in the script, like foo.sh file.txt or foo.sh dir/file.txt.



The create-or-overwrite behavior is much like the requirements for putting the file on the right side of the > output redirect operator, or passing it as an argument to tee (in fact, passing it as an argument to tee is exactly what I'm doing).



Before I get into the guts of the script, I want to make a reasonable check if the file can be created/overwritten, but not actually create it. This check doesn't have to be perfect, and yes I realize that the situation can change between the check and the point where the file is actually written - but here I'm OK with a best effort type solution so I can bail out early in the case that the file path is invalid.



Examples of reasons the file couldn't created:



  • the file contains a directory component, like dir/file.txt but the directory dir doesn't exist

  • the user doens't have write permissions in the specified directory (or the CWD if no directory was specified

Yes, I realize that checking permissions "up front" is no the UNIX way, rather I should just try the operation and ask forgiveness later. In my particular script however, this leads to a bad user experience and I can't change the responsible component.










share|improve this question















The user calls my script with a file path that will be either be created or overwritten at some point in the script, like foo.sh file.txt or foo.sh dir/file.txt.



The create-or-overwrite behavior is much like the requirements for putting the file on the right side of the > output redirect operator, or passing it as an argument to tee (in fact, passing it as an argument to tee is exactly what I'm doing).



Before I get into the guts of the script, I want to make a reasonable check if the file can be created/overwritten, but not actually create it. This check doesn't have to be perfect, and yes I realize that the situation can change between the check and the point where the file is actually written - but here I'm OK with a best effort type solution so I can bail out early in the case that the file path is invalid.



Examples of reasons the file couldn't created:



  • the file contains a directory component, like dir/file.txt but the directory dir doesn't exist

  • the user doens't have write permissions in the specified directory (or the CWD if no directory was specified

Yes, I realize that checking permissions "up front" is no the UNIX way, rather I should just try the operation and ask forgiveness later. In my particular script however, this leads to a bad user experience and I can't change the responsible component.







bash files error-handling






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 26 mins ago

























asked 3 hours ago









BeeOnRope

1628




1628











  • Will the argument always be based on the current directory or could the user specify a full path?
    – Jesse_b
    3 hours ago










  • @Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
    – BeeOnRope
    3 hours ago










  • @BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
    – Stéphane Chazelas
    2 hours ago










  • @StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
    – BeeOnRope
    2 hours ago
















  • Will the argument always be based on the current directory or could the user specify a full path?
    – Jesse_b
    3 hours ago










  • @Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
    – BeeOnRope
    3 hours ago










  • @BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
    – Stéphane Chazelas
    2 hours ago










  • @StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
    – BeeOnRope
    2 hours ago















Will the argument always be based on the current directory or could the user specify a full path?
– Jesse_b
3 hours ago




Will the argument always be based on the current directory or could the user specify a full path?
– Jesse_b
3 hours ago












@Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
– BeeOnRope
3 hours ago




@Jesse_b - I suppose the user could specify an absolute path like /foo/bar/file.txt. Basically I pass the path to tee like tee $OUT_FILE where OUT_FILE is passed on the command line. That should "just work" with both absolute and relative paths, right?
– BeeOnRope
3 hours ago












@BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
– Stéphane Chazelas
2 hours ago




@BeeOnRope, no you'd need tee -- "$OUT_FILE" at least. What if the file already exists or exists but is not a regular file (directory, symlink, fifo)?
– Stéphane Chazelas
2 hours ago












@StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
– BeeOnRope
2 hours ago




@StéphaneChazelas - well I am using tee "$OUT_FILE.tmp". If the file already exists, tee overwrites, which is the desired behavior in this case. If it's a directory, tee will fail (I think). symlink I'm not 100% sure?
– BeeOnRope
2 hours ago










5 Answers
5






active

oldest

votes

















up vote
4
down vote













The obvious test would be:



if touch /path/to/file; then
: it can be created
fi


But it does actually create the file if it's not already there. We could clean up after ourselves:



if touch /path/to/file; then
rm /path/to/file
fi


But this would remove a file that already existed, which you probably don't want.



We do, however, have a way around this:



if mkdir /path/to/file; then
rmdir /path/to/file
fi


You can't have a directory with the same name as another object in that directory. I can't think of a situation in which you'd be able to create a directory but not create a file. After this test, your script would be free to create a conventional /path/to/file and do whatever it pleases with it.






share|improve this answer



























    up vote
    1
    down vote













    I think DopeGhoti's solution is better but this should also work:



    file=$1
    if [[ "$file:0:1" == '/' ]]; then
    dir=$file%/*
    elif [[ "$file" =~ .*/.* ]]; then
    dir="$(PWD)/$file%/*"
    else
    dir=$(PWD)
    fi

    if [[ -w "$dir" ]]; then
    echo "writable"
    #do stuff with writable file
    else
    echo "not writable"
    #do stuff without writable file
    fi


    The first if construct checks if the argument is a full path (starts with /) and sets the dir variable to the directory path up to the last /. Otherwise if the argument does not start with a / but does contain a / (specifying a sub directory) it will set dir to the present working directory + the sub directory path. Otherwise it assumes the present working directory. It then checks if that directory is writable.






    share|improve this answer



























      up vote
      1
      down vote













      One option you might want to consider is creating the file early on but only populating it later in your script. You can use the exec command to open the file in a file descriptor (such as 3, 4, etc.) and then later use a redirection to a file descriptor (>&3, etc.) to write contents to that file.



      Something like:



      exec 3>dir/file.txt || 
      echo "Error creating dir/file.txt" >&2
      exit 1


      # long checks here
      check_ok ||
      echo "Failed checks" >&2
      # cleanup file before bailing out
      rm -f dir/file.txt
      exit 1


      # now populate the file, use a redirection to write
      # to the previously opened file descriptor
      populate_contents >&3


      You can also use a trap to clean up the file on error, that's a common practice.



      This way, you get a real check for permissions that you'll be able to create the file, while at the same time being able to perform it early enough that if that fails you haven't spent time waiting for the long checks.






      share|improve this answer




















      • I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
        – BeeOnRope
        3 hours ago










      • @DopeGhoti that doesn’t avoid the user getting upset.
        – Stephen Kitt
        2 hours ago










      • Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
        – Filipe Brandenburger
        2 hours ago

















      up vote
      1
      down vote













      From what I'm gathering, you want to check that when using



      tee -- "$OUT_FILE"


      (note the -- or it wouldn't work for file names that start with -), tee would succeed to open the file for writing.



      That is that:



      • the length of the file path doesn't exceed the PATH_MAX limit

      • the file exists (after symlink resolution) and is not of type directory and you have write permission to it.

      • if the file doesn't exist, the dirname of the file exists (after symlink resolution) as a directory and you have write and search permission to it and the filename length doesn't exceed the NAME_MAX limit of the filesystem that directory resides in.

      • or the file is a symlink that points to a file that doesn't exist and is not a symlink loop but meets the criteria just above

      We'll ignore for now filesystems like vfat, ntfs or hfsplus that have limitations on what byte values file names may contain, disk quota, process limit, selinux, apparmor or other security mechanism in the way, full filesystem, no inode left, device files that can't be opened that way for a reason or another, files that are executables currently mapped in some process address space all of which could also affect the ability to open or create the file.



      With zsh:



      zmodload zsh/system
      tee_would_likely_succeed()
      local file=$1 ERRNO=0
      if [ -d "$file" ]; then
      return 1 # directory
      elif [ -w "$file" ]; then
      return 0 # writable non-directory
      elif [ -e "$file" ]; then
      return 1 # exists, non-writable
      elif [ "$errnos[ERRNO]" != ENOENT ]; then
      return 1 # only ENOENT error can be recovered
      else
      local dir=$file:P:h base=$file:t
      [ -d "$dir" ] && # directory
      [ -w "$dir" ] && # writable
      [ -x "$dir" ] && # and searchable
      (($#base <= $(getconf -- NAME_MAX "$dir")))
      return
      fi





      share






















      • Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
        – BeeOnRope
        23 mins ago

















      up vote
      0
      down vote













      What about using normal test command like outlined below?



      FILE=$1

      DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

      if [ -d $DIR ] ; then
      echo "base directory $DIR for file exists"
      if [ -e $FILE ] ; then
      if [ -w $FILE ] ; then
      echo "file exists, is writeable"
      else
      echo "file exists, NOT writeable"
      fi
      elif [ -w $DIR ] ; then
      echo "directory is writeable"
      else
      echo "directory is NOT writeable"
      fi
      else
      echo "can NOT create file in non-existent directory $DIR "
      fi





      share|improve this answer




















        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',
        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
        );



        );













         

        draft saved


        draft discarded


















        StackExchange.ready(
        function ()
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f480656%2fhow-can-i-check-if-a-file-can-be-created-or-truncated-overwritten-in-bash%23new-answer', 'question_page');

        );

        Post as a guest






























        5 Answers
        5






        active

        oldest

        votes








        5 Answers
        5






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes








        up vote
        4
        down vote













        The obvious test would be:



        if touch /path/to/file; then
        : it can be created
        fi


        But it does actually create the file if it's not already there. We could clean up after ourselves:



        if touch /path/to/file; then
        rm /path/to/file
        fi


        But this would remove a file that already existed, which you probably don't want.



        We do, however, have a way around this:



        if mkdir /path/to/file; then
        rmdir /path/to/file
        fi


        You can't have a directory with the same name as another object in that directory. I can't think of a situation in which you'd be able to create a directory but not create a file. After this test, your script would be free to create a conventional /path/to/file and do whatever it pleases with it.






        share|improve this answer
























          up vote
          4
          down vote













          The obvious test would be:



          if touch /path/to/file; then
          : it can be created
          fi


          But it does actually create the file if it's not already there. We could clean up after ourselves:



          if touch /path/to/file; then
          rm /path/to/file
          fi


          But this would remove a file that already existed, which you probably don't want.



          We do, however, have a way around this:



          if mkdir /path/to/file; then
          rmdir /path/to/file
          fi


          You can't have a directory with the same name as another object in that directory. I can't think of a situation in which you'd be able to create a directory but not create a file. After this test, your script would be free to create a conventional /path/to/file and do whatever it pleases with it.






          share|improve this answer






















            up vote
            4
            down vote










            up vote
            4
            down vote









            The obvious test would be:



            if touch /path/to/file; then
            : it can be created
            fi


            But it does actually create the file if it's not already there. We could clean up after ourselves:



            if touch /path/to/file; then
            rm /path/to/file
            fi


            But this would remove a file that already existed, which you probably don't want.



            We do, however, have a way around this:



            if mkdir /path/to/file; then
            rmdir /path/to/file
            fi


            You can't have a directory with the same name as another object in that directory. I can't think of a situation in which you'd be able to create a directory but not create a file. After this test, your script would be free to create a conventional /path/to/file and do whatever it pleases with it.






            share|improve this answer












            The obvious test would be:



            if touch /path/to/file; then
            : it can be created
            fi


            But it does actually create the file if it's not already there. We could clean up after ourselves:



            if touch /path/to/file; then
            rm /path/to/file
            fi


            But this would remove a file that already existed, which you probably don't want.



            We do, however, have a way around this:



            if mkdir /path/to/file; then
            rmdir /path/to/file
            fi


            You can't have a directory with the same name as another object in that directory. I can't think of a situation in which you'd be able to create a directory but not create a file. After this test, your script would be free to create a conventional /path/to/file and do whatever it pleases with it.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered 3 hours ago









            DopeGhoti

            42.2k55180




            42.2k55180






















                up vote
                1
                down vote













                I think DopeGhoti's solution is better but this should also work:



                file=$1
                if [[ "$file:0:1" == '/' ]]; then
                dir=$file%/*
                elif [[ "$file" =~ .*/.* ]]; then
                dir="$(PWD)/$file%/*"
                else
                dir=$(PWD)
                fi

                if [[ -w "$dir" ]]; then
                echo "writable"
                #do stuff with writable file
                else
                echo "not writable"
                #do stuff without writable file
                fi


                The first if construct checks if the argument is a full path (starts with /) and sets the dir variable to the directory path up to the last /. Otherwise if the argument does not start with a / but does contain a / (specifying a sub directory) it will set dir to the present working directory + the sub directory path. Otherwise it assumes the present working directory. It then checks if that directory is writable.






                share|improve this answer
























                  up vote
                  1
                  down vote













                  I think DopeGhoti's solution is better but this should also work:



                  file=$1
                  if [[ "$file:0:1" == '/' ]]; then
                  dir=$file%/*
                  elif [[ "$file" =~ .*/.* ]]; then
                  dir="$(PWD)/$file%/*"
                  else
                  dir=$(PWD)
                  fi

                  if [[ -w "$dir" ]]; then
                  echo "writable"
                  #do stuff with writable file
                  else
                  echo "not writable"
                  #do stuff without writable file
                  fi


                  The first if construct checks if the argument is a full path (starts with /) and sets the dir variable to the directory path up to the last /. Otherwise if the argument does not start with a / but does contain a / (specifying a sub directory) it will set dir to the present working directory + the sub directory path. Otherwise it assumes the present working directory. It then checks if that directory is writable.






                  share|improve this answer






















                    up vote
                    1
                    down vote










                    up vote
                    1
                    down vote









                    I think DopeGhoti's solution is better but this should also work:



                    file=$1
                    if [[ "$file:0:1" == '/' ]]; then
                    dir=$file%/*
                    elif [[ "$file" =~ .*/.* ]]; then
                    dir="$(PWD)/$file%/*"
                    else
                    dir=$(PWD)
                    fi

                    if [[ -w "$dir" ]]; then
                    echo "writable"
                    #do stuff with writable file
                    else
                    echo "not writable"
                    #do stuff without writable file
                    fi


                    The first if construct checks if the argument is a full path (starts with /) and sets the dir variable to the directory path up to the last /. Otherwise if the argument does not start with a / but does contain a / (specifying a sub directory) it will set dir to the present working directory + the sub directory path. Otherwise it assumes the present working directory. It then checks if that directory is writable.






                    share|improve this answer












                    I think DopeGhoti's solution is better but this should also work:



                    file=$1
                    if [[ "$file:0:1" == '/' ]]; then
                    dir=$file%/*
                    elif [[ "$file" =~ .*/.* ]]; then
                    dir="$(PWD)/$file%/*"
                    else
                    dir=$(PWD)
                    fi

                    if [[ -w "$dir" ]]; then
                    echo "writable"
                    #do stuff with writable file
                    else
                    echo "not writable"
                    #do stuff without writable file
                    fi


                    The first if construct checks if the argument is a full path (starts with /) and sets the dir variable to the directory path up to the last /. Otherwise if the argument does not start with a / but does contain a / (specifying a sub directory) it will set dir to the present working directory + the sub directory path. Otherwise it assumes the present working directory. It then checks if that directory is writable.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered 3 hours ago









                    Jesse_b

                    10.6k22661




                    10.6k22661




















                        up vote
                        1
                        down vote













                        One option you might want to consider is creating the file early on but only populating it later in your script. You can use the exec command to open the file in a file descriptor (such as 3, 4, etc.) and then later use a redirection to a file descriptor (>&3, etc.) to write contents to that file.



                        Something like:



                        exec 3>dir/file.txt || 
                        echo "Error creating dir/file.txt" >&2
                        exit 1


                        # long checks here
                        check_ok ||
                        echo "Failed checks" >&2
                        # cleanup file before bailing out
                        rm -f dir/file.txt
                        exit 1


                        # now populate the file, use a redirection to write
                        # to the previously opened file descriptor
                        populate_contents >&3


                        You can also use a trap to clean up the file on error, that's a common practice.



                        This way, you get a real check for permissions that you'll be able to create the file, while at the same time being able to perform it early enough that if that fails you haven't spent time waiting for the long checks.






                        share|improve this answer




















                        • I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                          – BeeOnRope
                          3 hours ago










                        • @DopeGhoti that doesn’t avoid the user getting upset.
                          – Stephen Kitt
                          2 hours ago










                        • Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                          – Filipe Brandenburger
                          2 hours ago














                        up vote
                        1
                        down vote













                        One option you might want to consider is creating the file early on but only populating it later in your script. You can use the exec command to open the file in a file descriptor (such as 3, 4, etc.) and then later use a redirection to a file descriptor (>&3, etc.) to write contents to that file.



                        Something like:



                        exec 3>dir/file.txt || 
                        echo "Error creating dir/file.txt" >&2
                        exit 1


                        # long checks here
                        check_ok ||
                        echo "Failed checks" >&2
                        # cleanup file before bailing out
                        rm -f dir/file.txt
                        exit 1


                        # now populate the file, use a redirection to write
                        # to the previously opened file descriptor
                        populate_contents >&3


                        You can also use a trap to clean up the file on error, that's a common practice.



                        This way, you get a real check for permissions that you'll be able to create the file, while at the same time being able to perform it early enough that if that fails you haven't spent time waiting for the long checks.






                        share|improve this answer




















                        • I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                          – BeeOnRope
                          3 hours ago










                        • @DopeGhoti that doesn’t avoid the user getting upset.
                          – Stephen Kitt
                          2 hours ago










                        • Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                          – Filipe Brandenburger
                          2 hours ago












                        up vote
                        1
                        down vote










                        up vote
                        1
                        down vote









                        One option you might want to consider is creating the file early on but only populating it later in your script. You can use the exec command to open the file in a file descriptor (such as 3, 4, etc.) and then later use a redirection to a file descriptor (>&3, etc.) to write contents to that file.



                        Something like:



                        exec 3>dir/file.txt || 
                        echo "Error creating dir/file.txt" >&2
                        exit 1


                        # long checks here
                        check_ok ||
                        echo "Failed checks" >&2
                        # cleanup file before bailing out
                        rm -f dir/file.txt
                        exit 1


                        # now populate the file, use a redirection to write
                        # to the previously opened file descriptor
                        populate_contents >&3


                        You can also use a trap to clean up the file on error, that's a common practice.



                        This way, you get a real check for permissions that you'll be able to create the file, while at the same time being able to perform it early enough that if that fails you haven't spent time waiting for the long checks.






                        share|improve this answer












                        One option you might want to consider is creating the file early on but only populating it later in your script. You can use the exec command to open the file in a file descriptor (such as 3, 4, etc.) and then later use a redirection to a file descriptor (>&3, etc.) to write contents to that file.



                        Something like:



                        exec 3>dir/file.txt || 
                        echo "Error creating dir/file.txt" >&2
                        exit 1


                        # long checks here
                        check_ok ||
                        echo "Failed checks" >&2
                        # cleanup file before bailing out
                        rm -f dir/file.txt
                        exit 1


                        # now populate the file, use a redirection to write
                        # to the previously opened file descriptor
                        populate_contents >&3


                        You can also use a trap to clean up the file on error, that's a common practice.



                        This way, you get a real check for permissions that you'll be able to create the file, while at the same time being able to perform it early enough that if that fails you haven't spent time waiting for the long checks.







                        share|improve this answer












                        share|improve this answer



                        share|improve this answer










                        answered 3 hours ago









                        Filipe Brandenburger

                        5,0901624




                        5,0901624











                        • I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                          – BeeOnRope
                          3 hours ago










                        • @DopeGhoti that doesn’t avoid the user getting upset.
                          – Stephen Kitt
                          2 hours ago










                        • Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                          – Filipe Brandenburger
                          2 hours ago
















                        • I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                          – BeeOnRope
                          3 hours ago










                        • @DopeGhoti that doesn’t avoid the user getting upset.
                          – Stephen Kitt
                          2 hours ago










                        • Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                          – Filipe Brandenburger
                          2 hours ago















                        I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                        – BeeOnRope
                        3 hours ago




                        I had considered this, but the problem is that if another innocent error occurs somewhere between when I create the file and the point some time later where I would write to it, the user will probably be upset to find out that the specified file was overwritten.
                        – BeeOnRope
                        3 hours ago












                        @DopeGhoti that doesn’t avoid the user getting upset.
                        – Stephen Kitt
                        2 hours ago




                        @DopeGhoti that doesn’t avoid the user getting upset.
                        – Stephen Kitt
                        2 hours ago












                        Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                        – Filipe Brandenburger
                        2 hours ago




                        Hmmm, interesting... I had this idea of using set -o noclobber, in which case it would fail if the file already existed... But then I don't think it's possible to differentiate failure due to permissions (abort script) from failure due to file already existing (in which case you'd like to proceed and later overwrite the file.) If this was C or Python, etc., it's just a matter of opening the file with O_EXCL and looking for error EEXIST (permissions are fine, but file already exists) from EACCES (permission denied)... But not sure that can be done in bash...
                        – Filipe Brandenburger
                        2 hours ago










                        up vote
                        1
                        down vote













                        From what I'm gathering, you want to check that when using



                        tee -- "$OUT_FILE"


                        (note the -- or it wouldn't work for file names that start with -), tee would succeed to open the file for writing.



                        That is that:



                        • the length of the file path doesn't exceed the PATH_MAX limit

                        • the file exists (after symlink resolution) and is not of type directory and you have write permission to it.

                        • if the file doesn't exist, the dirname of the file exists (after symlink resolution) as a directory and you have write and search permission to it and the filename length doesn't exceed the NAME_MAX limit of the filesystem that directory resides in.

                        • or the file is a symlink that points to a file that doesn't exist and is not a symlink loop but meets the criteria just above

                        We'll ignore for now filesystems like vfat, ntfs or hfsplus that have limitations on what byte values file names may contain, disk quota, process limit, selinux, apparmor or other security mechanism in the way, full filesystem, no inode left, device files that can't be opened that way for a reason or another, files that are executables currently mapped in some process address space all of which could also affect the ability to open or create the file.



                        With zsh:



                        zmodload zsh/system
                        tee_would_likely_succeed()
                        local file=$1 ERRNO=0
                        if [ -d "$file" ]; then
                        return 1 # directory
                        elif [ -w "$file" ]; then
                        return 0 # writable non-directory
                        elif [ -e "$file" ]; then
                        return 1 # exists, non-writable
                        elif [ "$errnos[ERRNO]" != ENOENT ]; then
                        return 1 # only ENOENT error can be recovered
                        else
                        local dir=$file:P:h base=$file:t
                        [ -d "$dir" ] && # directory
                        [ -w "$dir" ] && # writable
                        [ -x "$dir" ] && # and searchable
                        (($#base <= $(getconf -- NAME_MAX "$dir")))
                        return
                        fi





                        share






















                        • Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                          – BeeOnRope
                          23 mins ago














                        up vote
                        1
                        down vote













                        From what I'm gathering, you want to check that when using



                        tee -- "$OUT_FILE"


                        (note the -- or it wouldn't work for file names that start with -), tee would succeed to open the file for writing.



                        That is that:



                        • the length of the file path doesn't exceed the PATH_MAX limit

                        • the file exists (after symlink resolution) and is not of type directory and you have write permission to it.

                        • if the file doesn't exist, the dirname of the file exists (after symlink resolution) as a directory and you have write and search permission to it and the filename length doesn't exceed the NAME_MAX limit of the filesystem that directory resides in.

                        • or the file is a symlink that points to a file that doesn't exist and is not a symlink loop but meets the criteria just above

                        We'll ignore for now filesystems like vfat, ntfs or hfsplus that have limitations on what byte values file names may contain, disk quota, process limit, selinux, apparmor or other security mechanism in the way, full filesystem, no inode left, device files that can't be opened that way for a reason or another, files that are executables currently mapped in some process address space all of which could also affect the ability to open or create the file.



                        With zsh:



                        zmodload zsh/system
                        tee_would_likely_succeed()
                        local file=$1 ERRNO=0
                        if [ -d "$file" ]; then
                        return 1 # directory
                        elif [ -w "$file" ]; then
                        return 0 # writable non-directory
                        elif [ -e "$file" ]; then
                        return 1 # exists, non-writable
                        elif [ "$errnos[ERRNO]" != ENOENT ]; then
                        return 1 # only ENOENT error can be recovered
                        else
                        local dir=$file:P:h base=$file:t
                        [ -d "$dir" ] && # directory
                        [ -w "$dir" ] && # writable
                        [ -x "$dir" ] && # and searchable
                        (($#base <= $(getconf -- NAME_MAX "$dir")))
                        return
                        fi





                        share






















                        • Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                          – BeeOnRope
                          23 mins ago












                        up vote
                        1
                        down vote










                        up vote
                        1
                        down vote









                        From what I'm gathering, you want to check that when using



                        tee -- "$OUT_FILE"


                        (note the -- or it wouldn't work for file names that start with -), tee would succeed to open the file for writing.



                        That is that:



                        • the length of the file path doesn't exceed the PATH_MAX limit

                        • the file exists (after symlink resolution) and is not of type directory and you have write permission to it.

                        • if the file doesn't exist, the dirname of the file exists (after symlink resolution) as a directory and you have write and search permission to it and the filename length doesn't exceed the NAME_MAX limit of the filesystem that directory resides in.

                        • or the file is a symlink that points to a file that doesn't exist and is not a symlink loop but meets the criteria just above

                        We'll ignore for now filesystems like vfat, ntfs or hfsplus that have limitations on what byte values file names may contain, disk quota, process limit, selinux, apparmor or other security mechanism in the way, full filesystem, no inode left, device files that can't be opened that way for a reason or another, files that are executables currently mapped in some process address space all of which could also affect the ability to open or create the file.



                        With zsh:



                        zmodload zsh/system
                        tee_would_likely_succeed()
                        local file=$1 ERRNO=0
                        if [ -d "$file" ]; then
                        return 1 # directory
                        elif [ -w "$file" ]; then
                        return 0 # writable non-directory
                        elif [ -e "$file" ]; then
                        return 1 # exists, non-writable
                        elif [ "$errnos[ERRNO]" != ENOENT ]; then
                        return 1 # only ENOENT error can be recovered
                        else
                        local dir=$file:P:h base=$file:t
                        [ -d "$dir" ] && # directory
                        [ -w "$dir" ] && # writable
                        [ -x "$dir" ] && # and searchable
                        (($#base <= $(getconf -- NAME_MAX "$dir")))
                        return
                        fi





                        share














                        From what I'm gathering, you want to check that when using



                        tee -- "$OUT_FILE"


                        (note the -- or it wouldn't work for file names that start with -), tee would succeed to open the file for writing.



                        That is that:



                        • the length of the file path doesn't exceed the PATH_MAX limit

                        • the file exists (after symlink resolution) and is not of type directory and you have write permission to it.

                        • if the file doesn't exist, the dirname of the file exists (after symlink resolution) as a directory and you have write and search permission to it and the filename length doesn't exceed the NAME_MAX limit of the filesystem that directory resides in.

                        • or the file is a symlink that points to a file that doesn't exist and is not a symlink loop but meets the criteria just above

                        We'll ignore for now filesystems like vfat, ntfs or hfsplus that have limitations on what byte values file names may contain, disk quota, process limit, selinux, apparmor or other security mechanism in the way, full filesystem, no inode left, device files that can't be opened that way for a reason or another, files that are executables currently mapped in some process address space all of which could also affect the ability to open or create the file.



                        With zsh:



                        zmodload zsh/system
                        tee_would_likely_succeed()
                        local file=$1 ERRNO=0
                        if [ -d "$file" ]; then
                        return 1 # directory
                        elif [ -w "$file" ]; then
                        return 0 # writable non-directory
                        elif [ -e "$file" ]; then
                        return 1 # exists, non-writable
                        elif [ "$errnos[ERRNO]" != ENOENT ]; then
                        return 1 # only ENOENT error can be recovered
                        else
                        local dir=$file:P:h base=$file:t
                        [ -d "$dir" ] && # directory
                        [ -w "$dir" ] && # writable
                        [ -x "$dir" ] && # and searchable
                        (($#base <= $(getconf -- NAME_MAX "$dir")))
                        return
                        fi






                        share













                        share


                        share








                        edited 1 hour ago

























                        answered 1 hour ago









                        Stéphane Chazelas

                        292k54544884




                        292k54544884











                        • Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                          – BeeOnRope
                          23 mins ago
















                        • Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                          – BeeOnRope
                          23 mins ago















                        Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                        – BeeOnRope
                        23 mins ago




                        Yes, you understood my intent correctly. In fact, the original question was unclear: I originally spoke about creating the file, but actually I am using it with tee so the requirement is really that the file can be created if it doesn't exist, or can be truncated to zero and overwritten if it does (or however else tee handles that).
                        – BeeOnRope
                        23 mins ago










                        up vote
                        0
                        down vote













                        What about using normal test command like outlined below?



                        FILE=$1

                        DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

                        if [ -d $DIR ] ; then
                        echo "base directory $DIR for file exists"
                        if [ -e $FILE ] ; then
                        if [ -w $FILE ] ; then
                        echo "file exists, is writeable"
                        else
                        echo "file exists, NOT writeable"
                        fi
                        elif [ -w $DIR ] ; then
                        echo "directory is writeable"
                        else
                        echo "directory is NOT writeable"
                        fi
                        else
                        echo "can NOT create file in non-existent directory $DIR "
                        fi





                        share|improve this answer
























                          up vote
                          0
                          down vote













                          What about using normal test command like outlined below?



                          FILE=$1

                          DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

                          if [ -d $DIR ] ; then
                          echo "base directory $DIR for file exists"
                          if [ -e $FILE ] ; then
                          if [ -w $FILE ] ; then
                          echo "file exists, is writeable"
                          else
                          echo "file exists, NOT writeable"
                          fi
                          elif [ -w $DIR ] ; then
                          echo "directory is writeable"
                          else
                          echo "directory is NOT writeable"
                          fi
                          else
                          echo "can NOT create file in non-existent directory $DIR "
                          fi





                          share|improve this answer






















                            up vote
                            0
                            down vote










                            up vote
                            0
                            down vote









                            What about using normal test command like outlined below?



                            FILE=$1

                            DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

                            if [ -d $DIR ] ; then
                            echo "base directory $DIR for file exists"
                            if [ -e $FILE ] ; then
                            if [ -w $FILE ] ; then
                            echo "file exists, is writeable"
                            else
                            echo "file exists, NOT writeable"
                            fi
                            elif [ -w $DIR ] ; then
                            echo "directory is writeable"
                            else
                            echo "directory is NOT writeable"
                            fi
                            else
                            echo "can NOT create file in non-existent directory $DIR "
                            fi





                            share|improve this answer












                            What about using normal test command like outlined below?



                            FILE=$1

                            DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

                            if [ -d $DIR ] ; then
                            echo "base directory $DIR for file exists"
                            if [ -e $FILE ] ; then
                            if [ -w $FILE ] ; then
                            echo "file exists, is writeable"
                            else
                            echo "file exists, NOT writeable"
                            fi
                            elif [ -w $DIR ] ; then
                            echo "directory is writeable"
                            else
                            echo "directory is NOT writeable"
                            fi
                            else
                            echo "can NOT create file in non-existent directory $DIR "
                            fi






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered 1 hour ago









                            Jaleks

                            1,118422




                            1,118422



























                                 

                                draft saved


                                draft discarded















































                                 


                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function ()
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f480656%2fhow-can-i-check-if-a-file-can-be-created-or-truncated-overwritten-in-bash%23new-answer', 'question_page');

                                );

                                Post as a guest













































































                                Comments

                                Popular posts from this blog

                                Long meetings (6-7 hours a day): Being “babysat” by supervisor

                                Is the Concept of Multiple Fantasy Races Scientifically Flawed? [closed]

                                Confectionery