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?

  • 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 '15 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 '15 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 '15 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 '15 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 '15 at 9:29
47

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$ 
  • 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 '15 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 '15 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 '15 at 9:36
  • @muru should work with set [+-]C for any modern shell – mikeserv Jan 12 '15 at 10:14
  • 3
    @Zen set +o noclobber in bash, or set +C. – Ruslan Jan 12 '15 at 10:29
7

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
}
  • 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 '15 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 '15 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 '15 at 10:52
  • 3
    @PM 2Ring, Today I learned many things from you, Thanks a lot.. I will fix them as soon as possible. – Sepahrad Salour Jan 12 '15 at 12:43
  • 1
    Nice work, Sepahrad! – PM 2Ring Jan 14 '15 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"
  • 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 '15 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 '15 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
    It's a Good Idea™ to test your code before posting it as answer... – PM 2Ring Jan 12 '15 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 – Gilles 'SO- stop being evil' Jan 13 '15 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 '15 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
  • Why am I not surprised... you're a sed maniac, Costas. :) (That's a compliment, BTW) – PM 2Ring Jan 12 '15 at 10:47
  • @PM2Ring Yes, I'm very dangerous! :P – Costas Jan 12 '15 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"! – G-Man Says 'Reinstate Monica' Aug 8 '15 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.