21

Recently I'm echoing short sentences to a tree_hole file.

I was using echo 'something' >> tree_hole to do this job.

But I was always worried of what if I mis-input of > instead of >>, since I did this often.

So I made a global bash func of my own in the bashrc:

function th { echo "$1" >> /Users/zen1/zen/pythonstudy/tree_hole; }
export -f th

But I'm wondering if there is another simple way to append lines to the end of a file. Because I may need to use that often in other occasions.

Is there any?

7
  • 9
    Will you not forget you are using the work-around each time you enter > ? Once I had an alias rm="rm -i" and in an other environment wrote rm * waiting for the confirmation questions. You are learning dangerous habits !
    – Walter A
    Jan 12, 2015 at 12:37
  • 9
    @WalterA er, his "workaround" doesn't let him type > instead of >>, he just runs "th some_sentence", which does nothing if the alias is undefined.
    – Random832
    Jan 12, 2015 at 14:11
  • 1
    @Ranom832 correct for his th work-around. The warning is for a "solution" like noclobber. When he uses something like noclobber in his normal shell he might use > when he is temporary root and wants to append something.
    – Walter A
    Jan 12, 2015 at 14:27
  • 7
    @Random832 & WalterA I normally don't say anything about this when I see it, but i figured maybe a friendly notice once in a while could be useful. Zen's user profile doesn't have much detail, so I'm not sure if you really know that his "workaround" is the correct form. Maybe you should say their "workaround" or the OP's "workaround". Perhaps you know Zen personally and you therefore know that his is correct, in which case, please pardon the noise. Not a big deal, I just mention it because I know I wouldn't have appreciated it very much if you had used that form to talk about me.
    – Celada
    Jan 12, 2015 at 14:56
  • 1
    @Zen, You wrote you are worried to mis-input > instead of >>. Celada's plan will take away the risks in your environment, and is a good solution for you. When you are helping your neighbour (who doesn't has noclobber or is using ksh) and you lost your attention for one or two > characters, you might accidentally overwrite one of his files. So everytime you get the noclobber warning in your own environment, thank God or Celada, say to yourself: Ohh, be carefully please!, shake your head and wait two seconds.
    – Walter A
    Jan 13, 2015 at 9:29

6 Answers 6

46

Set the shell's noclobber option:

bash-3.2$ set -o noclobber
bash-3.2$ echo hello >foo
bash-3.2$ echo hello >foo
bash: foo: cannot overwrite existing file
bash-3.2$ 
6
  • 3
    This is the definitive answer. Note that you can still overwrite the file if you force it with >|. zsh enables this behaviour by default.
    – orion
    Jan 12, 2015 at 9:17
  • 1
    @orion How do you en/disable it in zsh? I don't recall explicitly disabling it, but overwriting works fine on my zsh.
    – muru
    Jan 12, 2015 at 9:22
  • 2
    It's setopt noclobber and setopt clobber. It appears it isn't exactly "default", it depends on the config files that are shipped with your distro.
    – orion
    Jan 12, 2015 at 9:36
  • @muru should work with set [+-]C for any modern shell
    – mikeserv
    Jan 12, 2015 at 10:14
  • 3
    @Zen set +o noclobber in bash, or set +C.
    – Ruslan
    Jan 12, 2015 at 10:29
6

If you are worried your file will be damaged by > operator you can change your file attribute to append only:
In ext2/ext3/ext4 filesystem: chattr +a file.txt
In XFS filesystem: echo chattr +a | xfs_io file.txt

And if you want a function, I made a function for myself already (I used it in service file for logging outputs), You can change it for your purpose:

# This function redirect logs to file or terminal or both!
#@ USAGE: log option data
# To the file     -f file
# To the terminal -t
function log(){
        read -r data       # Read data from pipe line

        [[ -z ${indata} ]] && return 1    # Return 1 if data is null

        # Log to /var/log/messages
        logger -i -t SOFTWARE ${data}

        # While loop for traveling on the arguments
        while [[ ! -z "$*" ]]; do
                case "$1" in
                        -t)
                                # Writting data to the terminal
                                printf "%s\n" "${data}"
                                ;;
                        -f) 
                                # Writting (appending) data to given log file address
                                fileadd=$2
                                printf "%s %s\n" "[$(date +"%D %T")] ${data}" >> ${fileadd}
                                ;;
                        *)
                                ;;
                esac
                shift           # Shifting arguments
        done
}
6
  • 2
    There are a few problems with this script. 1) You should generally put double quotes around parameter expansions (stuff that starts with a $) to prevent globbing, word splitting and related whitespace problems. The online ShellCheck app can catch this and various other problems in bash scripts. On a related note "$@" is much safer than $*.
    – PM 2Ring
    Jan 12, 2015 at 10:39
  • 3
    2) You should normally call the read command with the -r option to prevent any backslashes in the input from escaping the following character. 3) It's usual in bash scripts to use lower case for script variable names since ALL UPPER CASE is used for system variables. So if you use upper case for your own variables you will confuse people who read your code, and you may accidentally clobber a system variable that you want to use later in the script. And if you source the script, it will clobber the variables for subsequent commands.
    – PM 2Ring
    Jan 12, 2015 at 10:44
  • 3
    4) echo is handy for printing fixed strings, but it can do unexpected things with arbitrary data, especially on GNU systems. It's far safer to use printf. See unix.stackexchange.com/questions/65803/… for more info, especially Stéphane Chazelas's answer.
    – PM 2Ring
    Jan 12, 2015 at 10:52
  • 3
    @PM 2Ring, Today I learned many things from you, Thanks a lot.. I will fix them as soon as possible. Jan 12, 2015 at 12:43
  • 1
    Nice work, Sepahrad!
    – PM 2Ring
    Jan 14, 2015 at 1:30
3

Use tee with the append option:

foo | tee -a some-file
# or
tee -a some-file <<EOF
blah blah
EOF
# or 
tee -a some-file <<<"blah blah"
2
  • 5
    Good idea, +1, but I imagine that if the OP is worried about forgetting a > character then they might be worried about forgetting the -a option!
    – Celada
    Jan 12, 2015 at 9:10
  • @Celada I suppose so (but I'd guess a typo from missing one keystroke in a set of repeated keystrokes is more likely to happen than forgetting an option).
    – muru
    Jan 12, 2015 at 9:11
0

many programs that can open files for overwriting can alternatively open them for append, eg gnu dd.

dd conv=notrunc oflag=append of=file

it can read stdin or a file named in the if= parameter add 2>/dev/null to suppress the byte count.

3
  • 3
    It's a Good Idea™ to test your code before posting it as answer...
    – PM 2Ring
    Jan 12, 2015 at 10:28
  • 1
    Recommending dd is a bad idea. dd does not reliably write its input to its output, not when one of them isn't a regular file or block device Jan 13, 2015 at 22:52
  • @gilles, you are misrepresenting the information at that link, when count is not specifued dd will copy all the data available, same as cat does.
    – Jasen
    Jan 14, 2015 at 5:40
-1

I'd like use a sed (even with backup-copy - see extention after -i):

sed -i.bak '$ a\something' /Users/zen1/zen/pythonstudy/tree_hole
3
  • Why am I not surprised... you're a sed maniac, Costas. :) (That's a compliment, BTW)
    – PM 2Ring
    Jan 12, 2015 at 10:47
  • @PM2Ring Yes, I'm very dangerous! :P
    – Costas
    Jan 12, 2015 at 12:07
  • Of course the way that works is to make a copy of the file, add the new text to the end, then delete the original file and rename the copy to the original name.  This (1) doesn't work if you don't have write access to the directory, (2) breaks links, and (3) is resource-expensive if the file is large.  Funny that you brought up the word "dangerous"! Aug 8, 2015 at 21:25
-1

You can always seek through the file in other ways...

seq 10000000 >f
{ wc -l >&2; echo new line\!; } <>f >&0; \
{ wc -l >&2; echo new line\!; } <>f >&0; \
{ wc -l >&2; echo new line\!; } <>f >&0; \
{ wc -l >&2; echo new line\!; } <>f >&0; \
{ wc -l >&2; echo new line\!; } <>f >&0; \
wc -l f; tail f

...that weird looking sequence prints:

10000000
10000001
10000002
10000003
10000004
10000005 f
9999996
9999997
9999998
9999999
10000000
new line!
new line!
new line!
new line!
new line!

But that's kinda silly.

A more useful example could look like:

 apnd() if    shift
        then  wc -l  >&2
              printf "$@"
        fi    <>"$1" >&0

You can call it like:

 apnd /path/to/file          \
      "${printf_fmt_string}" \
       arbitrary list of strings

And you'd wind up with a count of file's lines written to stderr just before the append action takes place.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .