views:

148

answers:

2

I did some havoc on my computer, when I played with the commands suggested by vezult [1]. I expected the one-liner to ask file-names to be removed. However, it immediately removed my files in a folder:

> find ./ -type f | while read x; do rm "$x"; done

I expected it to wait for my typing of stdin:s [2]. I cannot understand its action. How does the read command work, and where do you use it?

+4  A: 

What happened there is that read reads from stdin. When you put it at the end of a pipe, it read from that pipe.

So your find becomes

file1
file2

and so on; read reads that and replaces x successively with file1 then file2, and so your loop becomes

rm "file1" rm "file2"

and sure enough, that rm's every file starting at the current directory ".".

A couple hints.

You didn't need the "/".

It's better and safer to say

 find . -type f

because should you happen to type ". /" (ie, dot SPACE slash) find will start at the current directory and then go look starting at the root directory. That trick, given the right privileges, would delete every file in the computer. "." is already the name of a directory; you don't need to add the slash.

The find or rm commands will do this

It sounds like what you wanted to do was go through all the files in all the directories starting at the current directory ".", and have it ASK if you want to delete it. You could do that with

 find . -type f -exec rm -i {} \;

or

 find . -type f -ok rm  {} \;

and not need a loop at all. You can also do

 rm -r -i *

and get nearly the same effect, except that it will try to delete directories too. If the directory is empty, that'll even work.

Another thought

Come to think of it, unless you have a LOT of files, you could also do

rm -i `find . -type f`

Now the find in backquotes will become a bunch of file names on the command line, and the '-i' interactive flag on rm will ask the yes or no question.

Charlie Martin
Of course! Why on earth he does not use: > find . -type f -exec rm '{}' \; The while-thing makes my head hurting.
Masi
Just a beginner trying to make sense of UNIX. There's a lot of pages in section 1 of the manual.
Charlie Martin
+3  A: 

Charlie Martin gives you a good dissection and explanation of what went wrong with your specific example, but doesn't address the general question of:

When should you use the read command?

The answer to that is - when you want to read successive lines from some file (quite possibly the standard output of some previous sequence of commands in a pipeline), possibly splitting the lines into several separate variables. The splitting is done using the current value of '$IFS', which normally means on blanks and tabs (newlines don't count in this context; they separate lines). If there are multiple variables in the read command, then the first word goes into the first variable, the second into the second, ..., and the residue of the line into the last variable. If there's only one variable, the whole line goes into that variable.

There are many uses. This is one of the simpler scripts I have that uses the split option:

#!/bin/ksh
#
#   @(#)$Id: mkdbs.sh,v 1.4 2008/10/12 02:41:42 jleffler Exp $
#
#   Create basic set of databases

MKDUAL=$HOME/bin/mkdual.sql
ELEMENTS=$HOME/src/sqltools/SQL/elements.sql

cat <<! |
mode_ansi with log mode ansi
logged with buffered log
unlogged
stores with buffered log
!

while read dbs logging
do
    if [ "$dbs" = "unlogged" ]
    then bw=""; cw=""
    else bw="-ebegin"; cw="-ecommit"
    fi
    sqlcmd -xe "create database $dbs $logging" \
            $bw -e "grant resource to public" -f $MKDUAL -f $ELEMENTS $cw
done

The cat command with a here-document has its output sent to a pipe, so the output goes into the while read dbs logging loop. The first word goes into $dbs and is the name of the (Informix) database I want to create. The remainder of the line is placed into $logging. The body of the loop deals with unlogged databases (where begin and commit do not work), then run a program sqlcmd (completely separate from the Microsoft new-comer of the same name; it's been around since about 1990) to create a database and populate it with some standard tables and data - a simulation of the Oracle 'dual' table, and a set of tables related to the 'table of elements'.

Other scripts that use the read command are bigger (by far), but generally read lines containing one or more file names and some other attributes of relevance, and then apply an appropriate transform to the files using the attributes.

Osiris JL: file * | grep 'sh.*script' | sed 's/:.*//' | xargs wgrep read
esqlcver:read version letter
jlss:    while read directory
jlss:                read x || exit
jlss:            read x || exit
jlss:    while read file type link owner group perms
jlss:        read x || exit
jlss:    while read file type link owner group perms
kb: while read size name
mkbod:    while read directory
mkbod:while read dist comp
mkdbs:while read dbs logging
mkmsd:while read msdfile master
mknmd:while read gfile sfile version notes
publictimestamp:while read name type title
publictimestamp:while read name type title
Osiris JL:

'Osiris JL: ' is my command line prompt; I ran this in my 'bin' directory. 'wgrep' is a variant of grep that only matches entire words (to avoid words like 'already'). This gives some indication of how I've used it.

The 'read x || exit' lines are for an interactive script that reads a response from standard input, but exits if the command gets EOF (for example, if standard input comes from /dev/null).

Jonathan Leffler