简体   繁体   中英

One line bash command to find N oldest and newest files in a directory

I have the following command:

files=$(ls -lhrt dirname) && echo $files | head -5 && echo $files | tail -5

The idea is to return the oldest and newest 5 files in a directory dirname . This returns the requested data - however, the lines are jumbled together.

Is there a way to better format the output? (or perhaps a better way to write this functionality)?

Always quote variable expansions to prevent word splitting and globbing. When you leave $files unquoted bash's word splitting pass causes the newlines to be lost.

files=$(ls -lhrt dirname) && echo "$files" | head -5 && echo "$files" | tail -5

There's no real benefit from using the && operators. I'd just write:

files=$(ls -lhrt dirname)
echo "$files" | head -5
echo "$files" | tail -5

Or, better, swap the echo s for <<< to avoid unnecessary subprocesses.

files=$(ls -lhrt dirname)
head -5 <<< "$files"
tail -5 <<< "$files"

head an tail together

( Without having to store whole output of previous command into one variable )

Nota: along this, I will use top 4 lines and last 4 lines for sample using seq 1 100.. , but top 5 lines and last 5 lines for samples using ls -lhrt dirname .

First way, by using head and tail consecutively

if you try:

seq 1 100000 | (head -n 4;tail -n 4;)
1
2
3
4
99997
99998
99999
100000

Seem do the job, but

seq 1 1000 | (head -n 4;tail -n 4;)
1
2
3
4

Give wrong answer.

This is due to buffering , but let you use unbuffered input:

seq 1 12 | { for i in {1..4};do read foo;echo "$foo";done;tail -n 4 ;}
1
2
3
4
9
10
11
12

Finally

For your request, try this:

{ for i in {1..5};do read foo;echo "$foo";done;tail -n 5;} < <(ls -lhrt dirname) 

must match your need.

Or by using both together, with help of tee

Just look:

seq 1 12 | tee > >(tail -n4) >(head -n4)
1
2
3
4
9
10
11
12

But this could render strange things on terminal, to prevent this, you could just pipe whole to cat :

seq 1 12 | tee > >(tail -n4) >(head -n4) | cat
1
2
3
4
9
10
11
12

So

ls -lhrt dirname | tee > >(tail -n5) >(head -n5) | cat

must do the job.

Or even, if you wanna play with and a big variable:

files=$(seq 1 12) out='' in=''
for i in {1..4};do
    in+=${files%%$'\n'*}$'\n'
    files=${files#*$'\n'}
    out=${files##*$'\n'}$'\n'${out}
    files=${files%$'\n'*}
done
echo "$in${out%$'\n'}"
1
2
3
4
9
10
11
12

Then again:

files=$(ls -lhrt dirname) out='' in=''
for i in {1..5};do
    in+=${files%%$'\n'*}$'\n'
    files=${files#*$'\n'}
    out=${files##*$'\n'}$'\n'${out}
    files=${files%$'\n'*}
done
echo "$in${out%$'\n'}"

But you could use GNU

seq 1 100000 | sed -e ':a;N;4p;5,${s/^[^\n]*\n//;};$!ba;'
1
2
3
4
99997
99998
99999
100000

Then

ls -lhrt dirname | sed -e ':a;N;5p;6,${s/^[^\n]*\n//;};$!ba;'

What about adding linebreaks like so:

files=$(ls -lhrt dirname) && echo -e "${files}\n" | head -5 && echo -e "${files}\n" | tail -5

Explanation:

The -e flag enables echo to interpret escapes such as \\n in this example.

\\n itself is the escape sequence for "new line". So all it does is adding a new line after the echoed variable.

${ } is called Brace Expansion . Since I put the string in quotes, ${} will expand the variable to the string.

Even though it was requested for BASH, I just put here the ZSH line

echo dirname/*(.om[1,5]) dirname/*(.om[-5,-1])

This returns a list of files with the 5 oldest and 5 newest files (based on modification time). Other solutions based on ls -lrth might return directories or links or pipes or anything else.

You can replace echo with anything, but you requested a way to find the files, hence the correct answer in ZSH is the above glob (no echo )

It works like this :

  • dirname/* : take all mathching strings
  • ( : open glob specifier
  • . : return only plain files
  • om : sort them according to modification time
  • [1,5] return first five or [-5,-1] return last five
  • ) : close the glob specifier

More information on zsh globbing can be found here : http://www.bash2zsh.com/zsh_refcard/refcard.pdf

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM