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
  • 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. – David Yockey Sep 27 '19 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`? – David Yockey Sep 27 '19 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 https://www.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 Sep 27 '19 at 15:25

3 Answers3

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}'
cas
  • 1
  • 7
  • 119
  • 185
  • 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@` – Balaji_Pisces Sep 27 '19 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 Sep 27 '19 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. – Balaji_Pisces Sep 27 '19 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 Sep 27 '19 at 14:10
  • did you type lowercase `%c@` instead of upper-case `%C@` ? – cas Sep 27 '19 at 14:11
  • I typed uppercase `%C@` and again I ran it. Now the error looks different, `cut: invalid option -- 'z' Try 'cut --help' for more information head: invalid option -- 'z' Try 'cut --help' for more information` – Balaji_Pisces Sep 27 '19 at 14:15
  • OK, you've made one typo. you might have made others. carefully check that the command you ran exactly matches what's in my answer. fix as necessary. put it in a script file to make editing easy. – cas Sep 27 '19 at 14:16
  • I have checked very well. This `-z` seems reason for error. Because the error is, `cut: invalid option -- 'z' Try 'cut --help' for more information head: invalid option -- 'z' Try 'cut --help' for more information` I tried `cut --help` command and I couldn't find any values instead of `-z`. – Balaji_Pisces Sep 27 '19 at 14:31
  • what version of cut do you have? I have `cut (GNU coreutils) 8.30`. according to the coreutils changelog, GNU cut has supported `-z` since Jan 2016. – cas Sep 27 '19 at 14:32
  • Mine cut version is `cut (GNU coreutils) 8.22` and head version is also same. – Balaji_Pisces Sep 27 '19 at 14:36
  • you'll need to upgrade to at least coreutils 8.25 which was released on 2016-01-20 – cas Sep 27 '19 at 14:38
  • or use `awk -v RS=$'\0' 'NR <=10 {print $2}'` instead of `cut z -f2 | head -z -n 10` (head and tail got `-z` options the same day that `cut` did) – cas Sep 27 '19 at 14:40
  • Again, it comes this error `mv -t /home/balaji/regular_archieve/ home/balaji/work/file15 156934494 8954546690` – Balaji_Pisces Sep 27 '19 at 14:51
  • try the updated version at the bottom of my answer. and for file's sake, have the `echo` before the `mv` command until you're absolutely 100% sure that it's going to do what you want. – cas Sep 27 '19 at 15:01
  • This time its add up all the files in the error, `mv -t /home/balaji/regular_archieve/ home/balaji/work/file15 156934494 8954546690 home/balaji/work/file14 156934494 8954546690 home/balaji/work/file13 156934494 8954546690 home/balaji/work/file12 156934494 8954546690 .........home/balaji/work/file1 156934494 8954546690`. – Balaji_Pisces Sep 27 '19 at 15:20
  • have you got `%t` instead of `\t` in the middle of the `printf` format string? or `\n` instead of `\0`? that would be another typo. It has to be **exactly** `'%C@\t%p\0'` -- the timestamp, a TAB, the filename, and a NUL. – cas Sep 27 '19 at 15:24
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/99185/discussion-between-nagabalaji-vijayakumar-and-cas). – Balaji_Pisces Sep 27 '19 at 15:55
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.

  • 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 Oct 04 '19 at 04:16
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
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • 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. – Balaji_Pisces Oct 03 '19 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 Oct 03 '19 at 16:52
  • I trying solution for it. if I wouldn't to get solution,I will post this question/ – Balaji_Pisces Oct 04 '19 at 10:19