0

I am new to shell scripting. I am seeking solution for it. I should have maintain the 10 files in my directory, Incase any number of file is added to this directory. I just want to move the oldest file to other directory. I gave naming Work for my directory and regular_archieve for another directory. I have tried this,

work = /home/balaji/work
regular_archieve = /home/balaji/regular_archieve
cd $work
for i in 'ls -t | sed -e '1,10d' | xargs -d '\n' rm -f'
do
 mv $i $regular_archieve
done
if [ls -t | wc -1 > 10 | mv /home/balaji/regular-archieve]
 then
   echo "more than 10 n files"
fi
mv $(ls -t /home/balaji/work | sed -e '1,10d' | tail - 1) /home/balaji/regular_archieve

But I am not able to move to file to another directory.

I am receiving this error,

mv: cannot stat 'file' : No such file or directory
3
  • I'd recommend debugging a bit to find the problem... (1) The error suggests something's the matter with the value of $i and/or $regular_archieve in the mv statement, so... (2) check the values of $i and $regular_archieve to see if they're what you expect, and if not... (3) try to determine why and fix the problem; then, if you're still stuck... (4) edit your question to ask for help based on the additional information. Commented Sep 27, 2019 at 11:53
  • What do you get when you stick the lines echo $i and echo $regular_archive into the do loop before the mv? Commented Sep 27, 2019 at 11:53
  • Please post the actual code that you are using. The code that you have in your question will not give the error that you mention, but a bash: work: command not found error followed by several other errors (mostly due to simple syntax errors). You may want to consider testing your code in shellcheck.net Do this while you are writing it, not after finishing your script. Also test each command as you write them down in the script so that you know they do what you'd expect them to do.
    – Kusalananda
    Commented Sep 27, 2019 at 15:25

3 Answers 3

1

It's never a good idea to use the output of ls for anything except viewing in a terminal. See Why not parse ls (and what to do instead)?. Instead, use find:

find /home/balaji/work/ -type f -printf '%C@\t%p\0' |
  sort -z -k1,1 -r -n |
  cut -z -f2 |
  head -z -n 10 | 
  xargs -0r echo mv -t /home/balaji/regular_archive/

This requires the GNU versions of find, sort, cut, tail, and xargs (or, at least other versions of them that support the -z option for NUL record separators).

  • find uses -printf '%C@\t%p\0' to list the last-changed timestamps (%C@, in seconds since the epoch 1970-01-01 00:00:00) and filenames (%p) of all regular files. The fields are separated by a single tab (\t), and each record is separated by a NUL character (\0)
  • the output of find is piped into sort to reverse sort (-r) the files numerically (-n) on the first field only (-k 1,1) -- i.e. by the timestamp.
  • sort's output is piped into cut to delete the timestamp field (we no longer need it after we've finished sorting)
  • cut's output is piped into head to get the first ten entries
  • and finally, head's output is piped into xargs to run the mv command. This uses the GNU -t extension to mv, so that the target directory can be specified before the filenames.

Actually, this runs echo mv rather than mv, so it's a dry-run. Get rid of the echo when you're sure it's going to do what you want.

Note: This will work with any filenames, no matter what weird and annoying characters they might have in them (e.g. spaces, newlines, shell metacharacters, etc). Also, The file command has many other options which can be used to refine the search criteria.


If you have an old version of GNU coreutils (i.e. < version 8.25), neither cut nor head nor tail will have -z options. You can use awk instead. e.g.

find /home/balaji/work/ -type f -printf '%C@\t%p\0' |
  sort -z -k1,1 -r -n |
  awk -F '\t' 'BEGIN {RS=ORS="\0"}; NR<=10 { $1=""; $0=$0; $1=$1 ; print }' |
  xargs -0r echo mv -t /home/balaji/regular_archive/

Alternatively, you could use perl instead of awk:

 perl -F'\t' -0lane 'if ($. <= 10) {delete $F[0]; print @F}'
17
  • Thanks @cas for the answer and clear explanation along with, but still the files hasn't moved to another directory. I have received error like, mv -t /home/balaji/regular_archieve/ home/balaji/work/file15 Tue Sep 24 22:39:01.0895454669 2017@ Commented Sep 27, 2019 at 13:50
  • 1. does /home/balaji/regular_archieve/ exist? even with the mis-spelling? 2. That output is not possible with the find command in my answer. What did you do that was different? Did you try to pipe ls into it instead of using find? don't do that. 3. What version of find etc are you using? As mentioned, this requires GNU or similarly-capable versions. It won't work with other versions....including the versions found in, e.g., busybox or toybox
    – cas
    Commented Sep 27, 2019 at 13:58
  • 1. yes exists. 2. As you said know and then i didn't try by using ls. 3.find version is find (GNU findutils) 4.5.11 . Actually I am working in centos which is in VM VirtualBox. Commented Sep 27, 2019 at 14:07
  • the fact remains that that error message is not possible with the find command in my answer. it outputs ONLY the timestamp (in time_t seconds since epoch format) and the filename, separated by a TAB. It does not produce nicely formatted date strings with month and day names.
    – cas
    Commented Sep 27, 2019 at 14:10
  • did you type lowercase %c@ instead of upper-case %C@ ?
    – cas
    Commented Sep 27, 2019 at 14:11
0

I'm going to ignore the obvious syntax errors in your code, and assume that you want to keep the ten most recently modified files in the ~/work directory. I'm also going to assume that you have the zsh shell available.

The following zsh shell script would move all but the ten most recently modified files from ~/work to ~/regular_archieve.

#!/bin/zsh

mv -i $HOME/work/*(.om[11,-1]) $HOME/regular_archieve

The single command in the script leaves the ten most recently modified files in ~/work, and moves the rest to ~/regular_archieve. It does this using mv and by the help of a zsh-specific globbing pattern. The pattern *(.om[11,-1]) will expand to the regular files that needs to be moved (no directories will be matched). It's the om that sorts the files by modification timestamp, and [11,-1] that picks out filename 11 through to the oldest file in this sorted list. The dot at the start of the glob qualifier .om[11,-1] makes the glob only match regular files.

If you may have many thousands of files in the directory, this may generate an "argument list too long" error. In that case, move the files individually using a loop:

#!/bin/zsh

for pathname in $HOME/work/*(.om[11,-1]); do
    mv -i $pathname $HOME/regular_archieve
done
3
  • Thanks @Kusalananda for the explanation.. Is there any possible in this same scenario to move the file to another directory by calculating the size of the directory. For ex, If the directory exceeds 100mb of the files data, we have to move the oldest files to other directories. The directory has to maintain 100 mb, it should not be to hold the files more than 100 mb. Commented Oct 3, 2019 at 16:51
  • @NagabalajiVijayakumar I'd suggest that you ask a new question about that. Asking a new question would give you a bigger audience than just me and others that happen to be reading these comments.
    – Kusalananda
    Commented Oct 3, 2019 at 16:52
  • I trying solution for it. if I wouldn't to get solution,I will post this question/ Commented Oct 4, 2019 at 10:19
0

I found the another answer for the problem. The best to use find command instead of using ls command. It would help to find the oldest file and can use mv command to move to another directory.

mv $(find /home/balaji/work/ -type f -printf '%T+ %p\n' | sort | head -1) /home/balaji/regular_archieve

find – Search for files in a directory hierarchy.

/home/balaji/work/ – Search location.

/home/balaji/regular_archieve/ – Target location.

type -f – Searches only the regular files.

-printf ‘%T+ %p\n’ – Prints the file’s last modification date and time in separated by + symbol. (Eg. 2015-07-22+13:42:40.0000000000). Here, %p indicates the file name. \n indicates new line.

sort | head -n 1 – The sort command sorts the output and sends the output to head command to display the oldest file. Here, -n 1 indicates only one file i.e oldest file.

1
  • This will break on the first filename to contain a space or tab or shell metacharacter. that's why my answer went to the trouble of using NUL as separator - NUL and forward-slash / are the only two character that aren't allowed in unix filenames.
    – cas
    Commented Oct 4, 2019 at 4:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .