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"
( 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
.
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 bash 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
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.
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
ls -lhrt dirname | tee > >(tail -n5) >(head -n5) | cat
must do the job.
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
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'}"
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.