This will list the non-numeric data values in a mysql column.
1 | SELECT * FROM `tablename` WHERE concat('',`columnname` * 1) <> `columnname` |

This will list the non-numeric data values in a mysql column.
1 | SELECT * FROM `tablename` WHERE concat('',`columnname` * 1) <> `columnname` |

From one of my developers. This VBA (couldn’t find an approach in .Net) will loop through all the tables and write out the description from all the fields.
TODO’s include grabbing descriptions for tables and then tweaking below to create ALTER statements so that the comments can be applied to a server RDBMS after migration from Access (since almost none of the migration utilities I’ve seen migrate this documentation from Access).
Option Explicit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 'call readAllTables Public Function readAllTables() Dim DB As Database, tbl As TableDef, fld As DAO.Field Dim RS As Recordset Dim Table As String Dim allDesc As String Set DB = CurrentDb() For Each tbl In DB.TableDefs If Left$(tbl.Name, 4) <> "MSys" Then 'Debug.Print "In Table " & tbl.Name '& " " & tbl.DateCreated & " " & tbl.LastUpdated & " " & tbl.RecordCount allDesc = allDesc & vbNewLine & "Table:" & tbl.Name ' optional code to print all the fields On Error Resume Next For Each fld In tbl.Fields 'Debug.Print fld.Name allDesc = allDesc & vbNewLine & fld.Name & ":" & fld.Properties("Description") Next fld End If Next tbl WriteToATextFile (allDesc) End Function Sub WriteToATextFile(ByVal outputStr) 'first set a string which contains the path to the file you want to create. 'this example creates one and stores it in the root directory Dim MyFile As String MyFile = "c:\" & "TableFieldsWithDesc.txt" 'set and open file for output Dim fnum As Integer fnum = FreeFile() Open MyFile For Output As fnum 'write project info and then a blank line. Note the comma is required Write #fnum, outputStr Write #fnum, Close #fnum End Sub |
Some time ago I wrote down a little script to make a table from the MySQL information schema to describe your database. My eventual goal is to come close to reproducing a poor man’s database profiling script similar to this crude one ( http://www.ipcdesigns.com/data_profiling/), but perhaps less powerful and yet more elegant. I figure it’s going to take creating some procedures to loop through the chosen tables and columns.
Towards that end, I figure I need to take the contents of the handy view I made earlier and turn them into a table. Then if I execute some profiling queries, I can create tables from the results and join back to this summary table. So here is me persisting the view created earlier.You can do it this way:
1 2 | CREATE TABLE profiler_recs AS SELECT * FROM `v_field_table_data`; ALTER TABLE `mysql`.`profiler_recs` ADD COLUMN `PROFILE_RECS_ID` BIGINT(20) NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY(`PROFILE_RECS_ID`); |
or here is the resulting DDL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | CREATE TABLE `profiler_recs` ( `PROFILE_RECS_ID` BIGINT(20) NOT NULL AUTO_INCREMENT, `FIELD_NAME` VARCHAR(194) CHARACTER SET utf8 NOT NULL DEFAULT '', `SCHEMA_NAME` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `DEFAULT_CHARACTER_SET_NAME` VARCHAR(32) CHARACTER SET utf8 NOT NULL DEFAULT '', `DEFAULT_COLLATION_NAME` VARCHAR(32) CHARACTER SET utf8 NOT NULL DEFAULT '', `SQL_PATH` VARCHAR(512) CHARACTER SET utf8 DEFAULT NULL, `TABLE_CATALOG` VARCHAR(512) CHARACTER SET utf8 NOT NULL DEFAULT '', `TABLE_SCHEMA` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `TABLE_TYPE` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `ENGINE` VARCHAR(64) CHARACTER SET utf8 DEFAULT NULL, `VERSION` BIGINT(21) UNSIGNED DEFAULT NULL, `ROW_FORMAT` VARCHAR(10) CHARACTER SET utf8 DEFAULT NULL, `TABLE_ROWS` BIGINT(21) UNSIGNED DEFAULT NULL, `AVG_ROW_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `DATA_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `MAX_DATA_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `INDEX_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `DATA_FREE` BIGINT(21) UNSIGNED DEFAULT NULL, `AUTO_INCREMENT` BIGINT(21) UNSIGNED DEFAULT NULL, `CREATE_TIME` DATETIME DEFAULT NULL, `UPDATE_TIME` DATETIME DEFAULT NULL, `CHECK_TIME` DATETIME DEFAULT NULL, `TABLE_COLLATION` VARCHAR(32) CHARACTER SET utf8 DEFAULT NULL, `CHECKSUM` BIGINT(21) UNSIGNED DEFAULT NULL, `CREATE_OPTIONS` VARCHAR(255) CHARACTER SET utf8 DEFAULT NULL, `TABLE_COMMENT` VARCHAR(2048) CHARACTER SET utf8 NOT NULL DEFAULT '', `TABLE_NAME` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `COLUMN_NAME` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `ORDINAL_POSITION` BIGINT(21) UNSIGNED NOT NULL DEFAULT '0', `COLUMN_DEFAULT` LONGTEXT CHARACTER SET utf8, `IS_NULLABLE` VARCHAR(3) CHARACTER SET utf8 NOT NULL DEFAULT '', `DATA_TYPE` VARCHAR(64) CHARACTER SET utf8 NOT NULL DEFAULT '', `CHARACTER_MAXIMUM_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `CHARACTER_OCTET_LENGTH` BIGINT(21) UNSIGNED DEFAULT NULL, `NUMERIC_PRECISION` BIGINT(21) UNSIGNED DEFAULT NULL, `NUMERIC_SCALE` BIGINT(21) UNSIGNED DEFAULT NULL, `CHARACTER_SET_NAME` VARCHAR(32) CHARACTER SET utf8 DEFAULT NULL, `COLLATION_NAME` VARCHAR(32) CHARACTER SET utf8 DEFAULT NULL, `COLUMN_TYPE` LONGTEXT CHARACTER SET utf8 NOT NULL, `COLUMN_KEY` VARCHAR(3) CHARACTER SET utf8 NOT NULL DEFAULT '', `EXTRA` VARCHAR(27) CHARACTER SET utf8 NOT NULL DEFAULT '', `PRIVILEGES` VARCHAR(80) CHARACTER SET utf8 NOT NULL DEFAULT '', `COLUMN_COMMENT` VARCHAR(1024) CHARACTER SET utf8 NOT NULL DEFAULT '', PRIMARY KEY (`PROFILE_RECS_ID`) ) ENGINE=INNODB AUTO_INCREMENT=27399 DEFAULT CHARSET=latin1 |
I think you could design a procedure(returnExtents) that would accept a schema_name, then loop through all tables and columns by selecting from the view or table we created earlier and store the results as follows
Accept: SCHEMA_NAME
Return: max_value, min_value, num_nulls, max_length, min_length for each record in the above table. Or one record for each column in the schema.
Ideally you would write the results into a table as below
1 2 3 4 5 6 7 8 9 10 11 12 | CREATE TABLE `mysql.profile_rec_extents` ( `PROF_VALUE_RECS_ID` BIGINT(20) NOT NULL AUTO_INCREMENT, `PROFILE_RECS_ID` BIGINT(20) DEFAULT NULL, `MAX_VALUE` VARCHAR(250) DEFAULT NULL, `MIN_VALUE` VARCHAR(250) DEFAULT NULL, `NUM_NULLS` BIGINT(20) DEFAULT NULL, `MAX_LENGTH_CHARS` BIGINT(20) DEFAULT NULL, `MIN_LENGTH_CHARS` BIGINT(20) DEFAULT NULL, `MAX_LENGTH_BYTES` BIGINT(20) DEFAULT NULL, `MIN_LENGTH_BYTES` BIGINT(20) DEFAULT NULL, PRIMARY KEY (`PROF_VALUE_RECS_ID`) ) ENGINE=INNODB |
The following query returns somewhat useful information that could be used to populate the above table, but you’d have to loop it through every field.
1 2 3 4 5 6 7 | SELECT MIN(FIELD_NAME) AS MIN_VALUE, MAX(FIELD_NAME) AS MAX_VALUE, MAX(CHAR_LENGTH(FIELD_NAME)) AS MAX_CHARS, MIN(CHAR_LENGTH(FIELD_NAME)) AS MIN_CHARS, MAX(LENGTH(FIELD_NAME)) AS MAX_BYTES, MIN(LENGTH(FIELD_NAME)) AS MIN_BYTES FROM profiler_recs; |
Then, you do something like: for each VARCHAR field, where MAX_LENGTH <= 25
do
1 2 3 4 5 6 7 | SELECT COUNT(*) , `FIELDX` FROM `TABLEY` GROUP BY `FIELDX` ORDER BY COUNT(*) DESC; |
and load it into something like
1 2 3 4 5 6 7 | CREATE TABLE `mysql.profile_value_recs` ( `PROF_DOMAIN_RECS_ID` BIGINT(20) NOT NULL AUTO_INCREMENT, `PROFILE_RECS_ID` BIGINT(20) DEFAULT NULL, `VALUE` VARCHAR(250) DEFAULT NULL, `COUNT_VALUE` BIGINT(20) DEFAULT NULL, `RUN_DATETIME` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`PROF_DOMAIN_RECS_ID`)) ENGINE=INNODB |
After much deliberation we plunked down the plastic for an iPad2. It was supposed to be a gift for me.. though I likely use it the least of all people over the age of 2 in this house. It shipped much faster than expected and just in time for the trip we intended to take it on. The primary motivation was to leverage our increasing and annoyingly vendor-locked-in investment in music and video from our good friends at Apple. Our daughter knows how to jockey Apple devices as if born to them. I also liked the month-to-month nature of the Verizon plan we chose. The “pad” is snappy, has a quality build, and yes there are a billion apps in the wild that I have no use for.
But, I’ve been rockin this EVO 4G in all of its Android glory for over a year now and I just really want a larger version of it. But, I want to pay far less than the iPad for basically a device with the same physical properties and hardware capabilities. Then I get the flexibility of the Android OS, and less vendor lock-in. Oh, and I want a $20 month-to-month Verizon plan like the iPad has for business travel. I would have gotten the Android tablet in fact if (1) they were priced just a tad cheaper than iPads for the same apparent capabilities (2) I didn’t hear iffy stuff about build qualities and (3) all the 3G versions seemed to require annual or multi-year plans with the carriers.
You see, I enjoy (need) the flexibility of Android and I prefer how it works versus Apple’s intensely spartan “you don’t even need a back button, settings button, or left arrow key” approach. If I want to strip down Android to the point of being a one-button kiosk, I probably can. But Apple’s one size fits all poorage is driving me nuts. All the apps I need are on my phone. But I just need a bigger screen for reading and more disk for movies. I don’t need five apps that let me hone my skills at chosing veggies at the grocery. Want to look up the menu at the local fish house – nuts, it’s in Flash and Mr. Jobs wants to control his platform too much and doesn’t let me see Flash.
But, we own an Ipad now and I think I’ve figured out that what *I* really needed (not my kids) was a now out-of-vogue (that was so 2010) netbook with a month-to-month 3G card/dongle from my carrier. <sigh> one of just a few purchasing errors I’ve made recently.
Use this to grab the values after a certain value, of the whole string the value doesn’t exist. The concatenation ensure that the value is always found. The final Len(A1) forces the entire length of the string after the value to be shown regardless of what it it. the final A1 in the formula shows the entire string if the value is not found anywhere in the string.
IF(FIND(“.”,CONCATENATE(A1,”.”)) <= LEN(A1), MID(A1, FIND(“.”, A1) + 1,LEN(A1)), A1)
| hhhh.yuuuu | yuuuu |
| hhgff.1.2.3 | 1.2.3 |
| fgdfgf.fdgdfg.fgfd | fdgdfg.fgfd |
| t.6364y | 6364y |
| A888999 | A888999 |
Use this to find values between two values.
MID(A8,FIND(“(“,A8)+1,FIND(“)”,A8)-FIND(“(“,A8)-1)
| 555 (999-0000) | 999-0000 |
This one find text always to the right of a value (assumes within the last 5 chars) of a string.
| IF(FIND(“.”,CONCATENATE(A13,”.”)) <= LEN(A13),RIGHT(A13,FIND(“.”,RIGHT(A13,5))+2),A13) | |
| filename.doc | .doc |
| hfhg/fghfgh/hh | hfhg/fghfgh/hh |

If you try to convert the USGS geographic names (GNIS) text files from the website straight into shapefiles, you will have field name collisions because the field names willbe truncated to 10 characters which result in duplicate field names.
Run this script to rename certain fields to avoid this. Note that the entire US results in a file that is over 2 million records. Interestingly, but not surprisingly, the tools that seems to handle this size of data most gracefully is QGIS, not ArcMap.
#!/bin/sh
sed -i ‘s/DATE_CREATED/DT_CREATE/g’ GNISNationalFile.txt
sed -i ‘s/DATE_EDITED/DT_EDIT/g’ GNISNationalFile.txt
sed -i ‘s/FEATURE_CLASS/FEAT_CLASS/g’ GNISNationalFile.txt
sed -i ‘s/FEATURE_NAME/FEAT_NAME/g’ GNISNationalFile.txt
sed -i ‘s/PRIM_LAT_DEC/YLAT_DEC/g’ GNISNationalFile.txt
sed -i ‘s/PRIM_LONG_DMS/XLONG_DMS/g’ GNISNationalFile.txt
sed -i ‘s/PRIM_LONG_DEC/XLONG_DEC/g’ GNISNationalFile.txt
sed -i ‘s/PRIMARY_LAT_DMS/YLAT_DMS/g’ GNISNationalFile.txt
sed -i ‘s/SOURCE_LAT_DMS/SRC_Y_DMS/g’ GNISNationalFile.txt
sed -i ‘s/SOURCE_LAT_DEC/SRC_Y_DEC/g’ GNISNationalFile.txt
sed -i ‘s/SOURCE_LONG_DMS/SRC_X_DMS/g’ GNISNationalFile.txt
sed -i ‘s/SOURCE_LONG_DEC/SRC_X_DEC/g’ GNISNationalFile.txt
sed -i ‘s/STATE_ALPHA/STATE_NAME/g’ GNISNationalFile.txt
sed -i ‘s/STATE_NUMERIC/STATE_NUM/g’ GNISNationalFile.txt

1 | sudo apt-get remove --purge |
1 2 3 4 | apt-get install deborphan debfoster debfoster deborphan deborphan --guess-all |
1 2 3 4 5 | apt-get autoremove sudo apt-get remove --purge postgresql-client sudo apt-get remove --purge postgresql-client-8.4 sudo apt-get remove --purge postgresql-client-common apt-get clean |
still didn’t work.
1 2 3 4 5 | jcz@dell390:/usr/local/src/postgis-1.5.2$ ls -l /usr/bin/pg* -rwxr-xr-x 1 root root 26260 2011-02-02 03:56 /usr/bin/pg -rwxr-xr-x 1 root root 25912 2011-04-20 10:27 /usr/bin/pg_config -rwxr-xr-x 1 root root 13860 2010-07-06 20:21 /usr/bin/pgrep jcz@dell390:/usr/local/src/postgis-1.5.2$ sudo rm /usr/bin/pg_config |
then
1 2 | sudo ln -s /usr/local/pgsql/bin/pg_config /usr/bin/pg_config apt-get install libxml2-dev |
and finally got
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | PostGIS is now configured for i686-pc-linux-gnu -------------- Compiler Info ------------- C compiler: gcc -g -O2 C++ compiler: g++ -g -O2 -------------- Dependencies -------------- GEOS config: /usr/local/bin/geos-config GEOS version: 3.2.2 PostgreSQL config: /usr/bin/pg_config PostgreSQL version: PostgreSQL 9.0.4 PROJ4 version: 47 Libxml2 config: /usr/bin/xml2-config Libxml2 version: 2.7.7 PostGIS debug level: 0 -------- Documentation Generation -------- xsltproc: xsl style sheets: dblatex: convert: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #!/bin/sh sudo ln -s /usr/local/pgsql/bin/createlang /usr/bin/createlang sudo ln -s /usr/local/pgsql/bin/dropdb /usr/bin/dropdb sudo ln -s /usr/local/pgsql/bin/initdb /usr/bin/initdb sudo ln -s /usr/local/pgsql/bin/pg_ctl /usr/bin/pg_ctl sudo ln -s /usr/local/pgsql/bin/createdb /usr/bin/createdb sudo ln -s /usr/local/pgsql/bin/createuser /usr/bin/createuser sudo ln -s /usr/local/pgsql/bin/pg_dump /usr/bin/pg_dump sudo ln -s /usr/local/pgsql/bin/pgsql2shp /usr/bin/pgsql2shp sudo ln -s /usr/local/pgsql/bin/pg_upgrade /usr/bin/pg_upgrade sudo ln -s /usr/local/pgsql/bin/shp2pgsql /usr/bin/shp2pgsql /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data >logfile 2>&1 & /usr/local/pgsql/bin/createdb test /usr/local/pgsql/bin/psql test make make install createlang plpgsql yourtestdatabase psql -d yourtestdatabase -f postgis/postgis.sql psql -d yourtestdatabase -f spatial_ref_sys.sql |
how to generate a list of installed packages and use it to reinstall packages
1 2 3 4 | sudo apt-get update sudo apt-get dist-upgrade sudo dpkg --get-selections | grep -v deinstall | awk '{print $1}' > 164.ubuntu-files_b.txt sudo cat 164.ubuntu-files_b.txt | xargs sudo aptitude install |
NOTE: WordPress interprets two dashes (- -) as one dash (–). When you’re putting this into your CLI, make sure it’s dropping two dashes ‘- -’ without the space between them.
———————————————————————–
Update 20-Aug-2011
One liners install process instructions for Ubuntu 11
wget http://postgis.refractions.net/download/postgis-1.5.3.tar.gz
wget http://wwwmaster.postgresql.org/download/mirrors-ftp/source/v9.0.4/postgresql-9.0.4.tar.gz
wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2
wget http://download.osgeo.org/proj/proj-4.7.0.tar.gz
sudo apt-get install libreadline6-dev zlib1g-dev libxml2 libxml2-dev bison openssl libssl-dev
sudo apt-get yum install -y
mkdir -p /usr/local/src
cd /usr/local/src
tar zxvf postgresql-8.3.7.tar.gz
cd postgresql-8.3.7
./configure –with-openssl –enable-integer-datetimes
make
make install
cd /usr/local/src/postgresql-8.3.7/contrib/
make all
make install
cp /usr/local/src/postgresql-8.3.7/contrib/start-scripts/linux /etc/init.d/postgresql
chmod 775 /etc/init.d/postgresql
update-rc.d /etc/init.d/postgresql defaults
adduser postgres -d /usr/local/pgsql
echo ‘PATH=$PATH:/usr/local/pgsql/bin; export PATH’ > /etc/profile.d/postgresql.sh
echo ‘MANPATH=$MANPATH:/usr/local/pgsql/man; export MANPATH >> /etc/profile.d/pgmanual.sh
chmod 775 /etc/profile.d/postgresql.sh
chmod 775 /etc/profile.d/pgmanual.sh
mkdir -p /var/log/pgsql
chown -R postgres:postgres /var/log/pgsql/
mkdir /usr/local/pgsql/data
chown -R postgres:postgres /usr/local/pgsql/data
su – postgres
/usr/local/pgsql/bin/initdb -U postgres -E=UTF8 /usr/local/pgsql/data
These steps should be done as the postgres user. As root, issue: `su – postgres` (no password needed), the postgresql.conf and pg_hba.conf configuration files are located in /usr/local/pgsql/data/
Using the ‘nano‘ editor, (or vi), modify the postgresql.conf to allow the installation to listen for remote connections. Also, while we’re in here let’s configure the logging to create the log file in /var/log/pgsql/. The main cause of not being able to connect to a PostgreSQL database is because of a misconfiguration in this file.
listen_addresses = ‘*’
port = 5432
log_destination = ‘stderr’
logging_collector = on
log_directory = ‘/var/log/pgsql/’
log_filename = ‘postgresql-%Y-%m-%d’
log_line_prefix = ‘ %t %d %u ‘
Now, edit the pg_hba.conf and configure some network rules. Add the line in red to match your LAN address range. Set access from other computers to use md5 authentication. You can also set the other methods to md5, (and others) but for managability, leave the local connections set to ‘trust’ for now. The order of rules in this file matters.
# TYPE DATABASE USER CIDR-ADDRESS METHOD
# “local” is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
host all all 192.168.0.0/24 md5
# IPv6 local connections:
host all all ::1/128 trust
/etc/init.d/postgresql start

Useful little one liners. This one makes copy of subset of dir/ and below based on finding files that match the criteria. In this case, I wanted all .doc files copied into a single place.
I run most of this stuff in Windows on Cygwin, so I use the:
1 | <pre>-print0 | xargs -0 |
part to handle the spaces in file and directory names.
1 | <pre>find /cygdrive/f/dir1/ -name '*.doc' -print0 | xargs -0 cp -a --target-directory=/cygdrive/c/Temp --parents |
I never record this stuff and I always wish I did. So here’s a working MySQL config file that I’m using on a linux virtual machine with 2GB of memory. Notes and all so I don’t keep having to look stuff up.
# MySQL config file for APPSRV VPS with 2GB of memory
# 25-March 25-2011
# jcz.
# For MySQL 5.1
# The following options will be passed to all MySQL clients
[client]
port = 3306
socket = /var/lib/mysql/mysql.sock
Here are some simple bash scripts to list files into a text files that can be used to catalog stuff. Most of the time I use the last one.
1 2 3 | filer=$(find . -mtime -1) sizer=$(ls -lah $filer | awk '{ print $5"\t" $6"\t" $7"\t" $8"\t" $9"\t\n" }') echo $sizer |
or this one
1 2 | ls -ghG --full-time | awk '{ print $1"\t" $3 "\t" $4 "\t" $7 $8 $9 $10 $11 $12 "\n" }' > files.txt cat files.txt |
The script above makes output as follows. The extra lines just made it easier for me to read. This one will output files with spaces in the name by removing the spaces (lumping the words together). If you don’t like that, just put the ” ” between $7 $8 $9 etc. above.
[john.zastrow@appsrv ~]$ cat files.txt totaldrwxr-xr-x. 4.0K 2011-03-24 Desktopdrwxr-xr-x. 4.0K 2011-03-24 Documentsdrwxr-xr-x. 4.0K 2011-03-24 Downloads-rwxrwxrwx 126 2011-04-29 filer1.sh-rwxrwxrwx 158 2011-04-29 filer2.sh-rwxrwxrwx 123 2011-04-29 filer3.sh-rw-rw-r-- 0 2011-04-29 files.txt-rwxrwxrwx 73 2011-04-29 inter.sh
Then this script
1 2 3 4 5 | #!/bin/sh date > statfile.txt echo -e "File_type \t Modified_date \t Change_date \t File_bytes \t File_name" >> statfile.txt stat --printf "%F \t %y \t %z \t %s \t %N\n" * >> statfile.txt cat statfile.txt |
is just a little different and produces the following output. Notice that in both script I’m using tabs so these text files should come into a spreadsheet program nicely as below. Also notice the use of –printf so that I can embed the tabs right into stat’s output thereby more gracefully handling filenames with spaces in them (plus I quote them for good measure). A good use of this would be to combine find with it.