Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm using find -mtime -2 to find files modified in the past 24 hours or less. The output looks like this.

/home/user/logs/file-2014-08-22.log
/home/user/logs/file-2014-08-23.log

I need to save the first line of the output to a variable and then save the second line to a separate variable. I can't just pipe it. I know you might suggest | grep 22 or 23 but this is part of a bash script that will run many times, there will be a different set of files with different names next time so grep would be too specific. Could awk accomplish this? If so, how? .

share|improve this question
    
You have two lines output, how can you save third line? –  cuonglm Aug 25 at 4:59
    
Sorry there was originally the path followed by the files. Editing now. –  chubby_monky Aug 25 at 5:00

3 Answers 3

up vote 3 down vote accepted

Assuming that there are no spaces or the like in any of your filenames, there are a couple of ways of doing this. One is just to use an array:

files=( $(find -mtime -2) )
a=${files[1]}
b=${files[2]}

files will be an array of all the paths output by find in order, indexed from zero. You can get whichever lines you want out of that. Here I've saved the second and third lines into a and b, but you could use the array elements directly too.

An alternative if you have GNU find or another with the printf option is to use it in combination with read and process substitution:

read junk a b junk < <(find -printf '%p ')

This one turns all of find's output into a single line and then provides that line as the input to read, which saves the first word (path) into junk, the second into a, the third into b, and the rest of the line into junk again.

Similarly, you can introduce the paste command for the same effect on any POSIX-compatible system:

read junk a b junk < <(find -mtime -2 | paste -s)

paste -s will convert its input into a single tab-separated line, which read can deal with again.

In the general case, if you're happy to execute the main command more than once (not necessary here), you can use sed easily:

find | sed -n 2p

That will print only the second line of the output, by suppressing ordinary output with -n and selecting line 2 to print. You can also stitch together head and tail for the same effect, which will likely be more efficient in a very long file.

All of the above have the same effect of storing the second and third lines into a and b, and all still have the assumption that there are no spaces, tabs, newlines, or any other characters that happen to be in your input field separator (IFS) value in any of the filenames.


Note though that the output order of find is undefined, so "second file" isn't really a useful identifier unless you're organising them to be ordered some other way. It's likely to be something close to creation order in many cases, but not all.

share|improve this answer

With bash 4.x, you can use mapfile:

$ mapfile -t <<< "$(find -mtime -2)"
$ printf "%s\n" "${MAPFILE[0]}"
/home/user/logs/file-2014-08-22.log
$ printf "%s\n" "${MAPFILE[1]}"
/home/user/logs/file-2014-08-23.log

mapfile reads lines from the standard input into the indexed array variable, default array is MAPFILE. You can specify the name of array:

mapfile -t array <<< "$(find -mtime -2)"

-t option cause mapfile remove a trailing newline from each line read.

share|improve this answer
    
Can you expand slightly on what mapfile actually does? (i.e. put an single line from the man page here) –  Bernhard Aug 25 at 6:22
2  
@Bernhard: Updated answer. –  cuonglm Aug 25 at 6:28
    
Maybe I don't have bash 4.x because mapfile didn't seem to work for me. –  chubby_monky Aug 27 at 2:38
    
@chubby_monky: You can use bas --version to check. –  cuonglm Aug 27 at 4:13

Without restricting your self to a known number of files found or repeated entries in code or command line , you can use the following one line (though a long one) to assign sequential variable names to each output of find:

The line/script:

$ counter=0; for log_file in $(find -mtime -2); do echo $log_file; if [ -f $log_file ]; then ((counter++)); eval var_$counter=`readlink -f $log_file`; fi; done

Output (as in regular find), if the echo line isn't removed:

.
./logs
./logs/file-2014-08-22.log
./logs/file-2014-08-23.log

But, here I screen for valid files using if -f .... option (as described here), and hence the desired information is preserved:

$ echo $counter
2
$ echo $var_1
/home/shadowe/Videos/logs/file-2014-08-22.log
$ echo $var_2
/home/shadowe/Videos/logs/file-2014-08-23.log

Thus, sequential variables contain the absolute path of the find results and can be easily incorporated with an existing script.

Hope this helps.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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