0

I am actually using cygwin64. I would like to compress just a few selected files in a directory. Say, the 3 oldest files in the directory myDir. To get the 3 oldest files, this is what I do:

$ ls myDir -t | tail -3
file1
file2
file3

Now, the question is, how do I pass those 3 file(names) to, say tar or 7z?

Here's what I've tried:

7z a myFile.7z (ls myDir -t | tail -3)

but that doesn't work -- bash complains about the (. Removing it doesn't help, and neither does adding -- after myFile.7z. Same goes for tar instead of 7z.

djas
  • 973
  • 8
  • 24
  • 1
    You dropped a `$`. `7z a myFile.7z $(ls myDir -t | tail -3)` – Brian Cain Aug 16 '14 at 03:25
  • possible duplicate of [Understand pipe and redirection command](http://stackoverflow.com/questions/12394426/understand-pipe-and-redirection-command) – tripleee Aug 16 '14 at 06:06
  • possible duplicate of [How to gzip all files in all sub-directories in bash](http://stackoverflow.com/questions/10363921/how-to-gzip-all-files-in-all-sub-directories-in-bash) – jww Aug 16 '14 at 07:22

4 Answers4

2

Use xargs with 7z or tar after the ls/tail command. I don't have a linux terminal now to test but below should work ->

ls myDir -t | tail -3 | xargs 7z a myFile.7z 

Update -> To resolve path issue, here is the final solution.

ls -t -d -1 $PWD/myDir/* | tail -3 | xargs 7z a myFile.7z
dganesh2002
  • 1,917
  • 1
  • 26
  • 29
  • I like your style. Would be nice to add something like "${files[@]/#/$dir/}", similar to what @kosolebox added in his answer (since `ls` doesn't include the dir path). Would that be possible in your approach? – djas Aug 16 '14 at 04:28
  • I got your point. In that case use 'find' command instead of 'ls' command and your problem should be solved. I am not sure if you can still filter the files the way you can do it using ls. E.g. -> "find myDir "*" | xargs 7z a myFile.7z". Or else simply cd to myDir and run "ls -t | tail -3 | xargs 7z a myFile.7z" – dganesh2002 Aug 16 '14 at 04:38
  • Try this -> "ls -t -d -1 $PWD/myDir/* | tail -3 | xargs 7z a myFile.7z" – dganesh2002 Aug 16 '14 at 05:28
1

Try this form instead:

dir='myDir'
readarray -t files < <(exec ls "$dir" -t1 | tail -3)
7z a myFile.7z "${files[@]/#/$dir/}"
  • -1 makes filenames get enumerated line by line
  • readarray reads array of lines

  • "${files[@]/#/$dir/}" inserts dir path on every filename

Try this script too:

#!/bin/bash

FILE=$1
DIR=$2

if [[ $FILE != *.7z ]]; then
    echo "Invalid filename for 7z archive: $FILE"
    echo "Usage: $0 filename.7z dir"
    exit 1
elif [[ ! -d $DIR ]]; then
    echo "Not a directory: $DIR"
    echo "Usage: $0 filename.7z dir"
    exit 1
fi

[[ $FILE != /* ]] && FILE=$PWD/$1

cd "$DIR" || {
    echo "Unable to change directory to $DIR."
    exit 1
}

readarray -t TARGETS < <(exec ls -t1)

7za a "$FILE" "${TARGETS[@]:(-3)}"

Save it in a file like script.sh and run it with bash script.sh myFile.7z myDir.

Another way:

( cd myDir; 7za a ../myFile.7z $(ls -t1 | tail -3); )

Or

( cd myDir; ls -t1 | tail -3 | xargs -d '\n' -- 7za a ../myFile.7z; )
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • I seem to be getting a "syntax error close to 'token' unexpected '<'" – djas Aug 16 '14 at 04:02
  • @djas What do you get with `bash --version`? – konsolebox Aug 16 '14 at 04:16
  • "GNU bash, version 4.1.11(2)-release (x86_64-unknown-cygwin)" – djas Aug 16 '14 at 04:18
  • @djas. Runas `bash` first. Then run those commands. Also, try to execute those commands 1 by 1 just to avoid carriage returns. – konsolebox Aug 16 '14 at 04:20
  • I am clean of carriage returns, but `man readarray` returns "No manual entry for readarray". Do I need any package installed? (Sorry, what do you mean by "Runas `bash` first"?) – djas Aug 16 '14 at 04:23
  • `readarray` is a bash builtin. Try `help readarray`. You may be executing your shell in `/bin/sh` not `bash`, so run `bash` first. I meant "Run" not "Run as" sorry.. – konsolebox Aug 16 '14 at 04:24
  • @djas Try my script too. – konsolebox Aug 16 '14 at 04:32
  • it returns your message "Not a directory: myDir", where `myDir` is indeed a directory. – djas Aug 16 '14 at 04:42
  • @djas Yes. One line should be `elif [[ ! -d $DIR ]]; then` not `elif [[ -d $DIR ]]; then`. – konsolebox Aug 16 '14 at 04:47
  • yep, worked now; running it instead as `sh script.sh myFile.7z myDir` doesn't work. could you please explain? – djas Aug 16 '14 at 04:52
  • @djas What was working already? The script or the new suggestions? – konsolebox Aug 16 '14 at 04:58
  • the script worked; the new suggestions also (they were in fact the comment and answer of Brain Cain and dganesh2002). thanks! thinking of future users, I'll end up accepting dganesh2002's answer -- but I learned a lot from you today. thanks again! – djas Aug 16 '14 at 05:02
0

I haven't used 7z, but I think you are just missing the $ before ():

7z a myFile.7z $(ls myDir -t | tail -3)

You can check your command by typing:

echo $(ls -t *.txt | head -3)
beroe
  • 11,784
  • 5
  • 34
  • 79
0

Iterating over ls output can be unreliable

Here's one way you can accomplish what you want using stat.

declare -A files
for file in myDir/*; do
    modified=$(stat -c "%Y" "$file")
    files["$modified"]="$file"
done
top_3_indices=$(for i in ${!files[@]}; do echo $i; done | sort -rn | head -n 3)
for i in ${top_3_indices[@]}; do
    7z a myFile.7z "${files[$i]}"
done
John B
  • 3,566
  • 1
  • 16
  • 20