Thursday, September 19

A potential dangerous operation of "cp" command in linux

My ~/source/ folder has two files, "a" and "b". Now I am in ~/target/ folder and trying to copy 2 of them here:

~/target$ cp ../source/*

Yes I made a mistake. I intended to:
~/target$ cp ../source/* .

and I forgot the "." meaning to copy the files to current folder.

What is the result? "cp ../source/*" was expanded as "cp ../source/a ../source/b", so the ~/source/b file is replaced by a copy of a file, and there is no way to recover it any more!

If there were more than 2 files in that folder, the result would be different. For example, there were 3 files "a", "b", and "c". Then the "cp ../source/#" would have been expanded as "cp ../source/a ../source/b ../source/c", and the terminal would complaint:

cp: target `../source/c' is not a directory

At least there is some warnings, and the user will go back to check the command syntax. And there is no file being replaced (destroyed forever).

What if there is a folder in the source, and it is the "last" one? So far when I "ls", the name is sorted on alphabetic order, I guess the "cp" is using the same ordering to expand the "*" wild character. So let me create a folder "z" in the ~/source and execute the same command again:

~/target$ cp ../source/*

As expected, the terminal didn't complain anything, means the operation was successfully executed. The result is that all the files in the ../source/ are copied into ../source/z folder. Not exactly what I want.

So the feature of expanding the wild characters is dangerous when user input wrong parameters. It either: complain, copy files to unwanted place, or worse, replace an existing file unintentionally.

Suggestion: Change "cp" to complain if the user only input ONE parameter, before expanding the wild characters.

PS: After several minutes, I created a shell script thinking to replace the original /bin/cp:

if [ "$#" -eq 1 ]; then
echo "Usage: $0 at least 2 parameters" >&2
exit 1
echo $#
/bin/cp.origin $@
But no, it is not working. The shell expanded the wildcard character before it comes to the program. So there is no way to perform the task under such shell environment.

Tuesday, September 3

Insert the rank into table and more, MySql

After creating rank as in "Select the rows which have the max/min value in a table.", what I really want is to find out the changes of every record. So the first step is to actually write the rank into the table:

Alter table test add column serialnumber int, add column changefrompreviousrecord decimal(4, 2), add column changedays int;

Use variables to write the record:

update test
    set serialnumber =
         @sno := case when @pid<>personid then 0
                    else @sno+1
            end ,
        personid= @pid:=personid
order by personid, date_of_service;

The line of "personid=@pid:=personid " is a bit awkward, but that is the way to give value to @pid without affecting the logic.

After this update query, we have the real table of 

serialnumber, personid, value, date_of_service
0, 1,  23,  2013-01-05
1, 1,  29,  2013-01-05
2, 1,  27,  2013-01-04
3, 1,  28,  2013-01-04
4, 1,  26,  2013-01-03
0, 2,  25,  2013-01-02
1, 2,  24,  2013-01-01
So we can calculate "changefrompreviousrecord" and "changedays" using a self-joint:

update test bo1 join test bo2
    on bo1.personid=bo2.personidand bo1.serialnumber=bo2.serialnumber+1
set bo1.changefrompreviousrecord=(bo1.value-bo2.value)*100.0/bo2.value,
    bo1.changedays = datediff(bo1.date_of_service, bo2.date_of_service);

So we have:

serialnumber, personid, value, date_of_service, changefrompreviousrecord, changedays
0, 1,  23,  2013-01-05, -24.1, 0
1, 1,  29,  2013-01-05, 7.40, 1
2, 1,  27,  2013-01-04, -3.57, 0
3, 1,  28,  2013-01-04, 7.69, 1
4, 1,  26,  2013-01-03, null, null
0, 2,  25,  2013-01-02, 4.16, 1
1, 2,  24,  2013-01-01, null, null

Note, the "changefrompreviousrecord" is the percentage of the change from the previous record.

That is what I accomplished.