3

I am wondering why I do not get se same output from:

ls -1 -tF | head -n 1

and

echo $(ls -1 -tF | head -n 1)

I tried to get the last modified file, but using it inside a sub shell sometimes I get more than one file as result?

Why that and how to avoid?

Martin T.
  • 505
  • 5
  • 10
  • I do ls -tr | tail - n1 and this always works.. – MostWanted Oct 26 '17 at 13:23
  • 1
    quote your subshell, it will equate to `file*` for executables which is then expanded before being echoed. `-F, --classify append indicator (one of */=>@|) to entries` – 123 Oct 26 '17 at 13:25
  • Here the explanation for the -F: `function L { myvar=$1; h=${myvar:="1"}; echo "last ${h} modified file(s):"; export L=$(ls -1 -tF|fgrep -v / |head -n ${h}| sed 's/\(\*\|=\|@\)$//g' ); ls -l $L; } alias ol='L; xdg-open $L' ` – Martin T. Oct 26 '17 at 13:54
  • Don't forget to read about the dangers of [parsing `ls`](http://mywiki.wooledge.org/ParsingLs)... – ghoti Oct 26 '17 at 14:19
  • @ghoti: Thanks for the link, but is extending your local login shell (bash) behavior as bad? – Martin T. Oct 26 '17 at 14:28
  • That is the matter of much debate. :) On the one hand, folks tend to agree that it's generally a bad idea to establish bad habits. If there's a better way to do something, just do it the better way. On the other hand, you are in the best position to determine the risk of practices like this in your environment. If it works for you, that may be fine, especially if the only person who may get burned is you. :-) – ghoti Oct 26 '17 at 14:33

2 Answers2

6

The problem arises because you are using an unquoted subshell and -F flag for ls outputs shell special characters appended to filenames.

-F, --classify
append indicator (one of */=>@|) to entries

Executable files are appended with *.

When you run

echo $(ls -1 -tF | head -n 1)

then

$(ls -1 -tF | head -n 1)

will return a filename, and if it happens to be an executable and also be the prefix to another file, then it will return both.

For example if you have

test.sh
test.sh.backup

then it will return

test.sh*

which when echoed expands to

test.sh test.sh.backup

Quoting the subshell prevents this expansion

echo "$(ls -1 -tF | head -n 1)"

returns

test.sh*
123
  • 10,778
  • 2
  • 22
  • 45
  • 1
    great explanation.... using curly braces instead of parentheses is even better :) Like this : { ls -1 -tF | head -n 1 ; } no subshell is created – z atef Oct 26 '17 at 13:36
  • @zee Without the echo you could just run the commands as is without any braces/parenths – 123 Oct 26 '17 at 13:37
0

I just found the error: If you use echo $(ls -1 -tF | head -n 1) the file globing mechanism may result in additional matches.

So echo "$(ls -1 -tF | head -n 1)" would avoid this.

Because if the result is an executable it contains a * at the end.

I tried to place the why -F in a comment, but now I decided to put it here:

I added the following lines to my .bashrc, to have a shortcut to get last modified files or directories listed:

function L {
  myvar=$1; h=${myvar:="1"};
  echo "last ${h} modified file(s):"; 
  export L=$(ls -1 -tF|fgrep -v / |head -n ${h}| sed 's/\(\*\|=\|@\)$//g' );    
  ls -l $L;
}
function LD {
  myvar=$1;
  h=${myvar:="1"};
  echo "last ${h} modified directories:"; 
  export LD=$(ls -1 -tF|fgrep / |head -n $h | sed 's/\(\*\|=\|@\)$//g'); ls -ld $LD;
}
alias ol='L; xdg-open $L'
alias cdl='LD; cd $LD'

So now I can use L (or L 5) to list the last (last 5) modified files. But not directories.

And with L; jmacs $L I can open my editor, to edit it. Traditionally I used my alias lt='ls -lrt' but than I have to retype the name...

Now after mkdir ... I use cdl to change to that dir.

Martin T.
  • 505
  • 5
  • 10