listing file in unix and saving the output in a va

2019-09-11 03:24发布

问题:

This might be a very simple thing for a shell scripting programmer but am pretty new to it. I was trying to execute the below command in a shell script and save the output into a variable

inputfile=$(ls -ltr *.{PDF,pdf} | head -1 | awk '{print $9}')

The command works fine when I fire it from terminal but fails when executed through a shell script (sh). Why is that the command fails, does it mean that shell script doesn't support the command or am I doing it wrong? Also how do I know if a command will work in shell or not?

Just to give you a glimpse of my requirement, I was trying to get the oldest file from a particular directory (I also want to make sure upper case and lower case extensions are handled). Is there any other way to do this ?

回答1:

The above command will work correctly only if BOTH *.pdf and *.PDF files are in the directory you are currently. If you would like to execute it in a directory with only one of those you should consider using e.g.:

inputfiles=$(find . -maxdepth 1 -type f \( -name "*.pdf" -or -name "*.PDF" \) | xargs ls -1tr | head -1 )

NOTE: The above command doesn't work with files with new lines, or with long list of found files.



回答2:

Parsing ls is always a bad idea. You need another strategy.

How about you make a function that gives you the oldest file among the ones given as argument? the following works in Bash (adapt to your needs):

get_oldest_file() {
   # get oldest file among files given as parameters
   # return is in variable get_oldest_file_ret
   local oldest f
   for f do
      [[ -e $f ]] && [[ ! $oldest || $f -ot $oldest ]] && oldest=$f
   done
   get_oldest_file_ret=$oldest
}

Then just call as:

get_oldest_file *.{PDF,pdf}
echo "oldest file is: $get_oldest_file_ret"

Now, you probably don't want to use brace expansions like this at all. In fact, you very likely want to use the shell options nocaseglob and nullglob:

shopt -s nocaseglob nullglob
get_oldest_file *.pdf
echo "oldest file is: $get_oldest_file_ret"

If you're using a POSIX shell, it's going to be a bit trickier to have the equivalent of nullglob and nocaseglob.



回答3:

Is perl an option? It's ubiquitous on Unix.

I would suggest:

perl -e 'print ((sort { -M $b <=> -M $a } glob ( "*.{pdf,PDF}" ))[0]);'; 

Which:

  • uses glob to fetch all files matching the pattern.
  • sort, using -M which is relative modification time. (in days).
  • fetches the first element ([0]) off the sort.
  • Prints that.


回答4:

As @gniourf_gniourf says, parsing ls is a bad idea. Such as leaving unquoted globs, and generally not counting for funny characters in file names.

find is your friend:

#!/bin/sh
get_oldest_pdf() {
    #
    # echo path of oldest *.pdf (case-insensitive) file in current directory
    #
    find . -maxdepth 1 -mindepth 1 -iname "*.pdf" -printf '%T@ %p\n' \
      | sort -n \
      | tail -1 \
      | cut -d\   -f1-
}
whatever=$(get_oldest_pdf)

Notes:

  • find has numerous ways of formatting the output, including things like access time and/or write time. I used '%T@ %p\n', where %T@ is last write time in UNIX time format incl.fractal part. This will never containt space so it's safe to use as separator.

  • Numeric sort and tail get the last item, sorting by the time,

  • cut removes the time from the output.

  • I used IMO much easier to read/maintain pipe notation, with help of \.

  • the code should run on any POSIX shell,

  • You could easily adjust the function to parametrize the pattern, time used (access/write), control the search depth or starting dir.