2

I am writing this script that have this line:

ls -lrt /dir1/dir2/filename*.txt | tail -1 | awk '{print $9}' | read variable

What I wanted is to exit the script (without using if-statement) if it does not find any match.

drs
  • 5,453
area51
  • 21
  • You can certainly do it and I see a way as seen from here. http://stackoverflow.com/a/3068942/1742825. However, you need to add more details so that it can be modified to suit your requirements. – Ramesh Aug 14 '14 at 02:30
  • 3
    Why do you have a requirement that if statements can't be used? Can you use other conditional operators such as ||? – jordanm Aug 14 '14 at 02:33
  • yeah, I can use double pipe I tried this ls -lrt /dir1/dir2/filename*.txt | tail -1 | awk '{print $9}' | read variable || echo "exiting..."; exit but it doesn't work – area51 Aug 14 '14 at 02:36

2 Answers2

1

How to write this command

This particular task doesn't call for a pipe.

In zsh:

a=(/dir1/dir2/filename*.txt(Nom[1]))
if ((! #a)); then
  echo >&2 "No file matches /dir1/dir2/filename*.txt"
  exit 2
fi
variable=$a[1]

or, to exit the script automatically if the glob doesn't match:

set -e
a=(/dir1/dir2/filename*.txt(om[1]))
variable=$a[1]

Other shells don't have a built-in feature to find the most recent file. Calling ls is reasonable provided that your file names only contain printable characters other than newlines, but don't pass the -l option and then parse out all but the name, that's gratuitously complex and fragile (breaks on spaces, in particular). Also, take the first match of the -t sort, that's faster than taking the last match of the -tr sort. The straightforward approach:

variable=$(ls -td /dir1/dir2/filename*.txt 2>/dev/null | head -n 1)
if [ -z "$variable" ]; then
  echo >&2 "No file matches /dir1/dir2/filename*.txt"
  exit 2
fi

Handling pipes

If you want to abort your script when the left-hand side of a pipeline fails (i.e. exits with a nonzero status or due to a signal), in ksh93 and bash, you can set the pipefail option and exit if the status of the pipeline is nonzero.

set -e -o pipefail
somecommand | filter
echo "somecommand succeeded"

or

set -o pipefail
if ! somecommand | filter; then
  echo >&2 "somecommand or filter failed"
  exit 2
fi

Zsh doesn't have a pipefail option, but you can retrieve the status code of each component of the pipeline in the pipestatus array.

somecommand | filter
if ((pipestatus[1])); then
  echo >&2 "somecommand failed"
  exit 2
fi

In other shells, the status of the pipeline is the status of the right-hand command. There's no direct way to retrieve the status of the left-hand command. See Get exit status of process that's piped to another for some possible workarounds.

Beware that filter should read the whole output of somecommand, otherwise you need to handle the case when somecommand dies of a SIGPIPE.

If you want to act based on whether somecommand produces any output, rather than based on its exit status, you can use ifne from Joey Hess's moreutils. Note that most systems don't have these utilities installed by default.

  • touch 2 ; sleep 1 ; touch 1; for sh in mksh dash yash posh bash zsh 'busybox ash'; do command -p $sh -c '[ 1 -nt 2 ] || echo $0'; done 2>/dev/null prints only posh. So it might be more correct to say posh doesn't have a built-in feature to find the most recent file. And the most direct way to handle the return status of a pipe's left-hand command is to directly handle the return status of a pipe's left-hand command, signalling the parent shell as necessary - this is true in any shell. – mikeserv Aug 15 '14 at 20:11
0
{   ls -lrt /dir1/dir2/filename*.txt || 
    kill $$ 
} | tail -1 | 
awk '{print $9}' | 
read variable

Just kill yourself. Sometimes the shell can be a little whiny when I do it, but I find it doesnt even whimper if I do kill -PIPE $$.

Or if you would prefer to be specific about return codes and the rest:

trap 'echo some error >&2; exit 1' USR1 $and_or_other_signals
{ ls ... || kill -USR1 $$; } |...

Regarding the rest.. Maybe another way..?

touch ~/"a n\$ew file
in 'my home
directory"
v=$(ls -1rdt ~/* || kill -PIPE $$; echo /)
v=${v%?/*}; v=${v##*/}
printf '<%s>' ~/"$v"

OUTPUT

</home/mikeserv/a n$ew file
in 'my home
directory>

That will get you the name of the last modified file in any directory regardless of whatever characters it may contain. I use the < > to denote the begining and end of the variable and show that it does not contain any trailing white space. And the echo bit is to ensure that if there is trailing whitespace it persists.

It definitely beats the pipeline and it will still kill a scripted shell if ls returns other than 0 - such as when its command-line arguments do not exist.

The same can be done without reversing the sort order, but it is a little trickier, and it requires changing into the target directory to do it at all easily:

touch ~/"a n\$ew file
in 'my home
directory"
v=$(cd ~; ls -dtm ./* || kill -PIPE $$)
v=${v#*/}; v=${v%%,?./*}
printf '<%s>' ~/"$v"

OUTPUT

</home/mikeserv/a n$ew file
in 'my home
directory>

Another fairly portable method, I think:

set /[d]ir1/dir2/filename*.txt
[ -z "${1##/\[*}" ] && exit 1
while [ -n "$2" ] 
do  [ "$1" -nt "$2" ] &&
    set "$@" "$1"
shift; done; v=$1
printf %s\\n "$1" "$v"
mikeserv
  • 58,310