Canalice la salida de un comando si tiene éxito

14
INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml | head -1 | xargs basename`

Quería ejecutar el segundo comando ( head -1) solo si el primer comando es exitoso. ¿Cómo mejoro este comando?

Govind Kailas
fuente
¿Qué quieres decir con éxito? lsNo fallará.
Matteo
2
Pero el globo puede fallar si no hay archivos coincidentes.
tripleee

Respuestas:

8

Prueba esto:

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml | head -1 | xargs -r basename`

Al pasar xargsel -rindicador, solo se ejecutará basenamesi lee al menos un elemento de la entrada estándar ( head -1).

head -1 se ejecutará pero no verá ni capturará ningún resultado.

Además, si no desea que el usuario vea ningún resultado de error ls, puede redirigir lsla secuencia stderr de /dev/null.

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename`

También tenga en cuenta que agregué comillas alrededor $MY_DIR. De esa manera, el comando no fallará si $MY_DIRcontiene espacios.

Si está utilizando un shell moderno como bash, debe usar un $( )shell de captura en lugar de backticks. También debe considerar cambiar el estilo de sus variables. En general, debe evitar el uso de nombres de variables en mayúsculas en los scripts. Ese estilo está generalmente reservado para variables reservadas y ambientales.

input_file=$(ls -rt "$my_dir"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename)

fuente
¿No funciona esto si no hay archivos?
Mel Boyce
En realidad: ls -rt GLOB 2>/dev/null | head -n1 | xargs -r basenamefunciona.
Mel Boyce
@MelBoyce: He agregado eso a mi respuesta. Gracias.
ls es una herramienta para mirar interactivamente la información del archivo. Su salida está formateada para humanos y causará errores en los scripts. Usa globos o encuentra en su lugar. Comprenda por qué: mywiki.wooledge.org/ParsingLs
cinelli
@cinelli: Eso es verdad. Estaba respondiendo la pregunta.
9

En una tubería, todos los comandos se inician y se ejecutan simultáneamente, no uno tras otro. Por lo tanto, debe almacenar la salida en algún lugar.

if ls_output=$(ls -rtd -- "$MY_DIR"/FILE.*.xml); then
  first_file=$(printf '%s\n' "$ls_output" | head -n 1)
  first_file_name=$(basename -- "$first_file")
fi

Tenga en cuenta que se supone que los nombres de archivo no contienen caracteres de nueva línea. El uso xargstambién significaría problemas con caracteres en blanco, comillas simples y dobles y barras invertidas. Dejar variables sin comillas significaría problemas con el espacio, la tabulación y los caracteres comodín. Olvidar --significaría problemas con los nombres de archivo que comienzan con -.

Para obtener el nombre base del archivo más antiguo zshsin ninguna de esas restricciones en los caracteres (también evita el problema del tamaño limitado de los argumentos de un comando):

 first_file_name=($MY_DIR/FILE.*.xml(Om[1]:t))

Si no hay coincidencia, ese comando fallará y abortará el script. También puedes hacer:

 first_file_name=($MY_DIR/FILE.*.xml(NOm[1]:t))

En cuyo caso, la first_file_namematriz contendrá 1 elemento si hay una coincidencia o 0 si no. Entonces puedes hacer:

 if (($#first_file_name)); then
    printf 'Match: %s\n' $first_file_name
 else
    echo >&2 No match
 fi
Stéphane Chazelas
fuente
4

Encuentre el último archivo modificado en un directorio:

latest() {
  local file path=${1:-.} ext=${2-} latest
  for file in "${path%/}"/*"$ext"; do 
    [[ $file -nt $latest ]] && latest=$file
  done
  [[ $latest ]] && printf '%s\n' "$latest"
}

Uso: latest [directory/path/ [.extension]]

En lugar de llamar a basename, use la expansión de parámetros.

in_file=$(latest in/my/dir .xml)
base_fn=${in_file##*/} base_fn=${base_fn%.*}

En un directorio con estos contenidos:

foo.xml bar.xml baz.xml newest.xml

El contenido de la variable base_fn sería: newest

Para usar esto correctamente para cumplir con el propósito de su solicitud:

check_dir=/path/to/check
check_ext=.xml
if in_file=$(latest "$check_dir" "$check_ext"); then
  base_fn=${in_file##*/} base_fn=${base_fn%.*}
else
  printf '%s\n' "No file found in $check_dir" >&2
fi

EDITAR: al revisar la pregunta, me di cuenta de que el lscomando en cuestión está buscando el archivo más antiguo en un directorio. esta misma función podría renombrarse oldesty tener [[ $file -ot $oldest ]] && oldest=$fileque lograr el mismo efecto. disculpas por cualquier confusión.

Lo importante a tener en cuenta es que absolutamente, bajo ninguna circunstancia en la humanidad, no debe analizar la salida de ls. Nunca.

Josh McGee
fuente
Tenga en cuenta que, a diferencia de los lsenfoques basados ​​en, si hay enlaces simbólicos, se considerará el tiempo de modificación del objetivo de los enlaces simbólicos (agregue la -Lopción lspara obtener el mismo comportamiento allí).
Stéphane Chazelas
0

Creo que la mejor opción aquí es:

ls -rf $MY_DIR/FILE.*.xml
if [ $? -eq 0 ]; then
      INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml|head -1|xargs basename `
else
      echo "Error!"
fi

si no hay archivo, el código de retorno de ls es 2 pero si encuentra algún archivo será 0.

BitsOfNix
fuente
2
En lugar de comparar $?contra 0, usted puede hacer esto: if ls -rf $MY_DIR/FILE.*.xml ; then.
Además, puede redirigir la salida del primer comando a /dev/null. ls -rf $MY_DIR/FILE.*.xml &> /dev/null. De esa manera, el usuario no verá la salida adicional.