INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml | head -1 | xargs basename`

I wanted to execute the second command (head -1) only if the first command is successful. How do I improve this command?

  • What do you mean by successful? ls will not fail. – Matteo Jul 23 '13 at 13:12
  • 2
    But the glob can fail if there are no matching files. – tripleee Jul 23 '13 at 13:17

Try this:

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml | head -1 | xargs -r basename`

Passing xargs the -r flag will cause it to only run basename if reads at least one item from standard input (head -1).

head -1 will run but you won't see or capture any output from it.

Also, if you don't want the user to see any error output from ls, you can redirect ls's stderr stream to /dev/null.

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename`

Also note that I added quotation marks around $MY_DIR. That way, the command will not fail if $MY_DIR contains spaces.

If you're using a modern shell such as bash, you should use a $( ) capture shell instead of backticks. You should also consider changing the style of your variables. You should generally avoid using all-uppercase variable names in scripts. That style is generally reserved for reserved and environmental variables.

input_file=$(ls -rt "$my_dir"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename)
  • Doesn't this b0rk if there are no files? – Mel Boyce Jul 23 '13 at 13:44
  • Actually: ls -rt GLOB 2>/dev/null | head -n1 | xargs -r basename works. – Mel Boyce Jul 23 '13 at 13:48
  • @MelBoyce: I've added that to my answer. Thanks. – user26112 Jul 23 '13 at 13:51
  • ls is a tool for interactively looking at file information. Its output is formatted for humans and will cause bugs in scripts. Use globs or find instead. Understand why: mywiki.wooledge.org/ParsingLs – cinelli Jul 23 '13 at 15:56
  • @cinelli: That is true. I was just answering the question. – user26112 Jul 23 '13 at 16:09

In a pipe line, all commands are started and run concurrently, not one after the other. So you need to store the output somewhere.

if ls_output=$(ls -rtd -- "$MY_DIR"/FILE.*.xml); then
  first_file=$(printf '%s\n' "$ls_output" | head -n 1)
  first_file_name=$(basename -- "$first_file")

Note that it assumes file names don't contain newline characters. Using xargs would also mean problems with blank characters, single and double quotes and backslashes. Leaving variables unquoted would mean problems with space, tab and wildcard characters. Forgetting -- would mean problems with filenames starting with -.

To get the basename of the oldest file with zsh without any of those restrictions on characters (also avoids the problem of the limited size of arguments to a command):


If there's no match, that command will fail and abort the script. You can also do:


In which case the first_file_name array will contain 1 element if there's a match or 0 if not. You can then do:

 if (($#first_file_name)); then
    printf 'Match: %s\n' $first_file_name
    echo >&2 No match

Find the latest modified file in a directory:

latest() {
  local file path=${1:-.} ext=${2-} latest
  for file in "${path%/}"/*"$ext"; do 
    [[ $file -nt $latest ]] && latest=$file
  [[ $latest ]] && printf '%s\n' "$latest"

Usage: latest [directory/path/ [.extension]]

Instead of calling out to basename, use parameter expansion.

in_file=$(latest in/my/dir .xml)
base_fn=${in_file##*/} base_fn=${base_fn%.*}

In a directory with these contents:

foo.xml bar.xml baz.xml newest.xml

The contents of the base_fn variable would be: newest

To use this properly to serve the purpose of your request:

if in_file=$(latest "$check_dir" "$check_ext"); then
  base_fn=${in_file##*/} base_fn=${base_fn%.*}
  printf '%s\n' "No file found in $check_dir" >&2

EDIT: upon review of the question, i have realized that the ls command in question is looking for the oldest file in a directory. this same function could be renamed to oldest and have [[ $file -ot $oldest ]] && oldest=$file instead to achieve the same effect. apologies for any confusion.

the important thing to note is that you absolutely, under no circumstances ever in mankind, should not parse the output of ls. never.

  • Note that contrary to the ls-based approaches, if there are symlinks, the modification time of the target of the symlinks will be considered (add the -L option to ls to get the same behaviour there). – Stéphane Chazelas Jun 7 '16 at 5:29

I think that the best option here is:

ls -rf $MY_DIR/FILE.*.xml
if [ $? -eq 0 ]; then
      INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml|head -1|xargs basename `
      echo "Error!"

if there is no file then the return code of ls is 2 but if it finds some file will be 0.

  • 2
    Instead of comparing $? against 0, you can do this: if ls -rf $MY_DIR/FILE.*.xml ; then. – user26112 Jul 23 '13 at 13:27
  • Also, you can redirect the output of the first comand to /dev/null. ls -rf $MY_DIR/FILE.*.xml &> /dev/null. That way, the user won't see the extra output. – user26112 Jul 23 '13 at 13:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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